ghcide 1.5.0.1 → 1.6.0.0
raw patch · 97 files changed
+5259/−2518 lines, 97 filesdep +focusdep +list-tdep +randomdep −bytestring-encodingdep ~extradep ~hie-biosdep ~hls-graph
Dependencies added: focus, list-t, random, stm-containers
Dependencies removed: bytestring-encoding
Dependency ranges changed: extra, hie-bios, hls-graph, hls-plugin-api, implicit-hie-cradle, lsp, lsp-types
Files
- bench/lib/Experiments.hs +9/−12
- exe/Main.hs +7/−3
- ghcide.cabal +27/−14
- session-loader/Development/IDE/Session.hs +131/−32
- src/Control/Concurrent/Strict.hs +6/−5
- src/Development/IDE/Core/Actions.hs +10/−14
- src/Development/IDE/Core/Compile.hs +91/−80
- src/Development/IDE/Core/Debouncer.hs +14/−14
- src/Development/IDE/Core/FileExists.hs +18/−14
- src/Development/IDE/Core/FileStore.hs +10/−8
- src/Development/IDE/Core/OfInterest.hs +29/−10
- src/Development/IDE/Core/PositionMapping.hs +28/−21
- src/Development/IDE/Core/ProgressReporting.hs +47/−32
- src/Development/IDE/Core/RuleTypes.hs +14/−6
- src/Development/IDE/Core/Rules.hs +54/−27
- src/Development/IDE/Core/Service.hs +5/−4
- src/Development/IDE/Core/Shake.hs +159/−147
- src/Development/IDE/Core/Tracing.hs +70/−67
- src/Development/IDE/Core/UseStale.hs +162/−163
- src/Development/IDE/GHC/CPP.hs +63/−63
- src/Development/IDE/GHC/Compat.hs +129/−26
- src/Development/IDE/GHC/Compat/CPP.hs +204/−204
- src/Development/IDE/GHC/Compat/Core.hs +309/−91
- src/Development/IDE/GHC/Compat/Env.hs +11/−6
- src/Development/IDE/GHC/Compat/ExactPrint.hs +37/−0
- src/Development/IDE/GHC/Compat/Outputable.hs +20/−5
- src/Development/IDE/GHC/Compat/Parser.hs +62/−22
- src/Development/IDE/GHC/Compat/Plugins.hs +1/−2
- src/Development/IDE/GHC/Compat/Units.hs +17/−2
- src/Development/IDE/GHC/Compat/Util.hs +6/−0
- src/Development/IDE/GHC/Dump.hs +337/−0
- src/Development/IDE/GHC/Error.hs +2/−2
- src/Development/IDE/GHC/ExactPrint.hs +183/−47
- src/Development/IDE/GHC/Orphans.hs +28/−13
- src/Development/IDE/GHC/Util.hs +48/−29
- src/Development/IDE/Import/DependencyInformation.hs +2/−2
- src/Development/IDE/LSP/LanguageServer.hs +82/−52
- src/Development/IDE/LSP/Notifications.hs +3/−2
- src/Development/IDE/LSP/Outline.hs +94/−26
- src/Development/IDE/Main.hs +38/−23
- src/Development/IDE/Main/HeapStats.hs +53/−0
- src/Development/IDE/Plugin/CodeAction.hs +172/−84
- src/Development/IDE/Plugin/CodeAction/Args.hs +17/−3
- src/Development/IDE/Plugin/CodeAction/ExactPrint.hs +266/−65
- src/Development/IDE/Plugin/Completions.hs +17/−29
- src/Development/IDE/Plugin/Completions/Logic.hs +73/−46
- src/Development/IDE/Plugin/Completions/Types.hs +18/−0
- src/Development/IDE/Plugin/Test.hs +4/−2
- src/Development/IDE/Plugin/TypeLenses.hs +15/−12
- src/Development/IDE/Spans/AtPoint.hs +41/−33
- src/Development/IDE/Spans/Documentation.hs +224/−213
- src/Development/IDE/Spans/LocalBindings.hs +140/−140
- src/Development/IDE/Spans/Pragmas.hs +432/−0
- src/Development/IDE/Types/Diagnostics.hs +1/−1
- src/Development/IDE/Types/Exports.hs +6/−4
- src/Development/IDE/Types/HscEnvEq.hs +153/−151
- src/Development/IDE/Types/Logger.hs +1/−1
- src/Development/IDE/Types/Shake.hs +9/−3
- src/Generics/SYB/GHC.hs +124/−124
- src/Text/Fuzzy/Parallel.hs +1/−1
- test/data/cabal-exe/dist-newstyle/build/x86_64-osx/ghc-8.10.3/a-0.1.0.0/x/a/build/a/autogen/Paths_a.hs +0/−50
- test/data/import-placement/MultiLinePragma.expected.hs +13/−0
- test/data/import-placement/MultiLinePragma.hs +12/−0
- test/data/import-placement/OptionsNotAtTopWithSpaces.expected.hs +16/−0
- test/data/import-placement/OptionsNotAtTopWithSpaces.hs +15/−0
- test/data/import-placement/OptionsPragmaNotAtTop.expected.hs +8/−0
- test/data/import-placement/OptionsPragmaNotAtTop.hs +7/−0
- test/data/import-placement/PragmaNotAtTopMultipleComments.expected.hs +23/−0
- test/data/import-placement/PragmaNotAtTopMultipleComments.hs +22/−0
- test/data/import-placement/PragmaNotAtTopWithCommentsAtTop.expected.hs +18/−0
- test/data/import-placement/PragmaNotAtTopWithCommentsAtTop.hs +17/−0
- test/data/import-placement/PragmaNotAtTopWithImports.expected.hs +19/−0
- test/data/import-placement/PragmaNotAtTopWithImports.hs +18/−0
- test/data/import-placement/PragmaNotAtTopWithModuleDecl.expected.hs +22/−0
- test/data/import-placement/PragmaNotAtTopWithModuleDecl.hs +21/−0
- test/data/import-placement/ShebangNotAtTop.expected.hs +10/−0
- test/data/import-placement/ShebangNotAtTop.hs +9/−0
- test/data/import-placement/ShebangNotAtTopNoSpace.expected.hs +8/−0
- test/data/import-placement/ShebangNotAtTopNoSpace.hs +7/−0
- test/data/import-placement/ShebangNotAtTopWithSpaces.expected.hs +21/−0
- test/data/import-placement/ShebangNotAtTopWithSpaces.hs +20/−0
- test/data/import-placement/WhereDeclLowerInFile.expected.hs +18/−0
- test/data/import-placement/WhereDeclLowerInFile.hs +17/−0
- test/data/import-placement/WhereKeywordLowerInFileNoExports.expected.hs +16/−0
- test/data/import-placement/WhereKeywordLowerInFileNoExports.hs +15/−0
- test/data/multi/dist-newstyle/build/x86_64-osx/ghc-8.10.2/a-1.0.0/build/autogen/Paths_a.hs +0/−50
- test/data/multi/dist-newstyle/build/x86_64-osx/ghc-8.10.3/a-1.0.0/build/autogen/Paths_a.hs +0/−50
- test/data/multi/dist-newstyle/build/x86_64-osx/ghc-8.10.3/b-1.0.0/build/autogen/Paths_b.hs +0/−50
- test/data/plugin-knownnat/cabal.project +3/−0
- test/data/symlink/hie.yaml +10/−0
- test/data/symlink/some_loc/Sym.hs +4/−0
- test/data/symlink/src/Foo.hs +4/−0
- test/exe/HieDbRetry.hs +127/−0
- test/exe/Main.hs +355/−106
- test/exe/Progress.hs +39/−7
- test/src/Development/IDE/Test.hs +10/−2
- test/src/Development/IDE/Test/Diagnostic.hs +1/−1
bench/lib/Experiments.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE ImplicitParams #-} {-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE PolyKinds #-} {-# OPTIONS_GHC -Wno-deprecations -Wno-unticked-promoted-constructors #-} module Experiments@@ -194,7 +195,7 @@ let edit :: TextDocumentContentChangeEvent =TextDocumentContentChangeEvent { _range = Just Range {_start = bottom, _end = bottom} , _rangeLength = Nothing, _text = t}- bottom = Position maxBoundUinteger 0+ bottom = Position maxBound 0 t = T.unlines ["" ,"holef :: [Int] -> [Int]"@@ -213,7 +214,7 @@ flip allM docs $ \DocumentPositions{..} -> do bottom <- pred . length . T.lines <$> documentContents doc diags <- getCurrentDiagnostics doc- case requireDiagnostic diags (DsError, (bottom, 8), "Found hole", Nothing) of+ case requireDiagnostic diags (DsError, (fromIntegral bottom, 8), "Found hole", Nothing) of Nothing -> pure True Just _err -> pure False )@@ -251,7 +252,7 @@ <*> option auto (long "timeout" <> value 60 <> help "timeout for waiting for a ghcide response") <*> ( Example "name" <$> (Right <$> packageP)- <*> (some moduleOption <|> pure ["Distribution/Simple.hs"])+ <*> (some moduleOption <|> pure ["src/Distribution/Simple.hs"]) <*> pure [] <|> Example "name"@@ -263,7 +264,7 @@ packageP = ExamplePackage <$> strOption (long "example-package-name" <> value "Cabal")- <*> option versionP (long "example-package-version" <> value (makeVersion [3,4,0,0]))+ <*> option versionP (long "example-package-version" <> value (makeVersion [3,6,0,0])) pathP = strOption (long "example-path") versionP :: ReadM Version@@ -404,7 +405,7 @@ ++ ["--verbose" | verbose ?config] ++ ["--ot-memory-profiling" | Just _ <- [otMemoryProfiling ?config]] lspTestCaps =- fullCaps {_window = Just $ WindowClientCapabilities $ Just True}+ fullCaps {_window = Just $ WindowClientCapabilities (Just True) Nothing Nothing } conf = defaultConfig { logStdErr = verbose ?config,@@ -585,7 +586,7 @@ doc <- openDoc m "haskell" -- Setup the special positions used by the experiments- lastLine <- length . T.lines <$> documentContents doc+ lastLine <- fromIntegral . length . T.lines <$> documentContents doc changeDoc doc [TextDocumentContentChangeEvent { _range = Just (Range (Position lastLine 0) (Position lastLine 0)) , _rangeLength = Nothing@@ -638,9 +639,9 @@ return res where loop pos- | _line pos >= lll =+ | (fromIntegral $ _line pos) >= lll = return Nothing- | _character pos >= lengthOfLine (_line pos) =+ | (fromIntegral $ _character pos) >= lengthOfLine (fromIntegral $ _line pos) = loop (nextLine pos) | otherwise = do checks <- checkDefinitions pos &&^ checkCompletions pos@@ -663,7 +664,3 @@ checkCompletions pos = not . null <$> getCompletions doc pos --- | We don't have a uinteger type yet. So hardcode the maxBound of uinteger, 2 ^ 31 - 1--- as a constant.-maxBoundUinteger :: Int-maxBoundUinteger = 2147483647
exe/Main.hs view
@@ -7,7 +7,7 @@ import Arguments (Arguments (..), getArguments)-import Control.Monad.Extra (unless, whenJust)+import Control.Monad.Extra (unless) import Data.Default (def) import Data.Version (showVersion) import Development.GitRev (gitHash)@@ -50,13 +50,17 @@ if argsVersion then ghcideVersion >>= putStrLn >> exitSuccess else hPutStrLn stderr {- see WARNING above -} =<< ghcideVersion - whenJust argsCwd IO.setCurrentDirectory+ -- if user uses --cwd option we need to make this path absolute (and set the current directory to it)+ argsCwd <- case argsCwd of+ Nothing -> IO.getCurrentDirectory+ Just root -> IO.setCurrentDirectory root >> IO.getCurrentDirectory let logPriority = if argsVerbose then Debug else Info arguments = if argsTesting then Main.testing else Main.defaultArguments logPriority Main.defaultMain arguments- {Main.argCommand = argsCommand+ { Main.argsProjectRoot = Just argsCwd+ , Main.argCommand = argsCommand ,Main.argsLogger = Main.argsLogger arguments <> pure telemetryLogger ,Main.argsRules = do
ghcide.cabal view
@@ -2,7 +2,7 @@ build-type: Simple category: Development name: ghcide-version: 1.5.0.1+version: 1.6.0.0 license: Apache-2.0 license-file: LICENSE author: Digital Asset and Ghcide contributors@@ -13,7 +13,7 @@ A library for building Haskell IDE's on top of the GHC API. homepage: https://github.com/haskell/haskell-language-server/tree/master/ghcide#readme bug-reports: https://github.com/haskell/haskell-language-server/issues-tested-with: GHC == 8.6.5 || == 8.8.3 || == 8.8.4 || == 8.10.5 || == 8.10.6 || == 8.10.7 || == 9.0.1+tested-with: GHC == 8.6.5 || == 8.8.4 || == 8.10.6 || == 8.10.7 || == 9.0.1 || == 9.0.2 || == 9.2.1 extra-source-files: README.md CHANGELOG.md test/data/**/*.project test/data/**/*.cabal@@ -49,22 +49,23 @@ dependent-sum, dlist, exceptions,- -- we can't use >= 1.7.10 while we have to use hlint == 3.2.*- extra >= 1.7.4 && < 1.7.10,+ extra >= 1.7.4, fuzzy, filepath, fingertree,+ focus, ghc-exactprint, ghc-trace-events, Glob, haddock-library >= 1.8 && < 1.11, hashable, hie-compat ^>= 0.2.0.0,- hls-plugin-api ^>= 1.2.0.2,+ hls-plugin-api ^>= 1.2.0.2 || ^>= 1.3, lens,+ list-t, hiedb == 0.4.1.*,- lsp-types >= 1.3.0.1 && < 1.4,- lsp == 1.2.*,+ lsp-types ^>= 1.4.0.1,+ lsp ^>= 1.4.0.0 , monoid-subclasses, mtl, network-uri,@@ -72,15 +73,17 @@ parallel, prettyprinter-ansi-terminal, prettyprinter,+ random, regex-tdfa >= 1.3.1.0, retrie, rope-utf16-splay, safe, safe-exceptions,- hls-graph ^>= 1.5.1,+ hls-graph ^>= 1.6, sorted-list, sqlite-simple, stm,+ stm-containers, syb, text, time,@@ -92,7 +95,6 @@ hslogger, Diff ^>=0.4.0, vector,- bytestring-encoding, opentelemetry >=0.6.1, heapsize ==0.3.*, unliftio,@@ -103,8 +105,8 @@ ghc-check >=0.5.0.4, ghc-paths, cryptohash-sha1 >=0.11.100 && <0.12,- hie-bios >= 0.7.1 && < 0.9.0,- implicit-hie-cradle >= 0.3.0.5 && < 0.4,+ hie-bios >= 0.8 && < 0.9.0,+ implicit-hie-cradle ^>= 0.3.0.5 || ^>= 0.5, base16-bytestring >=0.1.1 && <1.1 if os(windows) build-depends:@@ -114,7 +116,6 @@ unix default-extensions:- ApplicativeDo BangPatterns DeriveFunctor DeriveGeneric@@ -144,6 +145,7 @@ Development.IDE Development.IDE.Main Development.IDE.Core.Actions+ Development.IDE.Main.HeapStats Development.IDE.Core.Debouncer Development.IDE.Core.FileStore Development.IDE.Core.IdeConfiguration@@ -160,6 +162,7 @@ Development.IDE.GHC.Compat Development.IDE.GHC.Compat.Core Development.IDE.GHC.Compat.Env+ Development.IDE.GHC.Compat.ExactPrint Development.IDE.GHC.Compat.Iface Development.IDE.GHC.Compat.Logger Development.IDE.GHC.Compat.Outputable@@ -168,6 +171,7 @@ Development.IDE.GHC.Compat.Units Development.IDE.GHC.Compat.Util Development.IDE.Core.Compile+ Development.IDE.GHC.Dump Development.IDE.GHC.Error Development.IDE.GHC.ExactPrint Development.IDE.GHC.Orphans@@ -183,6 +187,7 @@ Development.IDE.Spans.Documentation Development.IDE.Spans.AtPoint Development.IDE.Spans.LocalBindings+ Development.IDE.Spans.Pragmas Development.IDE.Types.Diagnostics Development.IDE.Types.Exports Development.IDE.Types.HscEnvEq@@ -297,7 +302,8 @@ -rtsopts -- disable idle GC -- increase nursery size- "-with-rtsopts=-I0 -A128M"+ -- Enable collection of heap statistics+ "-with-rtsopts=-I0 -A128M -T" main-is: Main.hs build-depends: hiedb,@@ -381,16 +387,21 @@ hls-plugin-api, network-uri, lens,+ list-t, lsp-test ^>= 0.14, optparse-applicative, process, QuickCheck, quickcheck-instances,+ random, rope-utf16-splay, regex-tdfa ^>= 1.3.1, safe, safe-exceptions, shake,+ sqlite-simple,+ stm,+ stm-containers, hls-graph, tasty, tasty-expected-failure,@@ -399,7 +410,7 @@ tasty-rerun, text, unordered-containers,- if (impl(ghc >= 8.6))+ if (impl(ghc >= 8.6) && impl(ghc < 9.2)) build-depends: record-dot-preprocessor, record-hasfield@@ -413,6 +424,7 @@ Experiments Experiments.Types Progress+ HieDbRetry default-extensions: BangPatterns DeriveFunctor@@ -442,6 +454,7 @@ base, bytestring, containers,+ data-default, directory, extra, filepath,
session-loader/Development/IDE/Session.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} {-|@@ -11,6 +12,8 @@ ,setInitialDynFlags ,getHieDbLoc ,runWithDb+ ,retryOnSqliteBusy+ ,retryOnException ) where -- Unfortunately, we cannot use loadSession with ghc-lib since hie-bios uses@@ -41,7 +44,7 @@ import Data.Time.Clock import Data.Version import Development.IDE.Core.RuleTypes-import Development.IDE.Core.Shake+import Development.IDE.Core.Shake hiding (withHieDb) import qualified Development.IDE.GHC.Compat as Compat import Development.IDE.GHC.Compat.Core hiding (Target, TargetFile, TargetModule,@@ -73,17 +76,21 @@ import System.Info import Control.Applicative (Alternative ((<|>)))-import Control.Exception (evaluate) import Data.Void -import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.Stats (atomically, modifyTVar',+ readTVar, writeTVar) import Control.Concurrent.STM.TQueue+import Data.Foldable (for_) import qualified Data.HashSet as Set import Database.SQLite.Simple import Development.IDE.Core.Tracing (withTrace)+import Development.IDE.Types.Shake (WithHieDb) import HieDb.Create import HieDb.Types import HieDb.Utils+import System.Random (RandomGen)+import qualified System.Random as Random -- | Bump this version number when making changes to the format of the data stored in hiedb hiedbDataVersion :: String@@ -164,28 +171,118 @@ mapM_ setUnsafeGlobalDynFlags dynFlags pure libdir +-- | If the action throws exception that satisfies predicate then we sleep for+-- a duration determined by the random exponential backoff formula,+-- `uniformRandom(0, min (maxDelay, (baseDelay * 2) ^ retryAttempt))`, and try+-- the action again for a maximum of `maxRetryCount` times.+-- `MonadIO`, `MonadCatch` are used as constraints because there are a few+-- HieDb functions that don't return IO values.+retryOnException+ :: (MonadIO m, MonadCatch m, RandomGen g, Exception e)+ => (e -> Maybe e) -- ^ only retry on exception if this predicate returns Just+ -> Logger+ -> Int -- ^ maximum backoff delay in microseconds+ -> Int -- ^ base backoff delay in microseconds+ -> Int -- ^ maximum number of times to retry+ -> g -- ^ random number generator+ -> m a -- ^ action that may throw exception+ -> m a+retryOnException exceptionPred logger maxDelay !baseDelay !maxRetryCount rng action = do+ result <- tryJust exceptionPred action+ case result of+ Left e+ | maxRetryCount > 0 -> do+ -- multiply by 2 because baseDelay is midpoint of uniform range+ let newBaseDelay = min maxDelay (baseDelay * 2)+ let (delay, newRng) = Random.randomR (0, newBaseDelay) rng+ let newMaxRetryCount = maxRetryCount - 1+ liftIO $ do+ logWarning logger $ "Retrying - " <> makeLogMsgComponentsText (Right delay) newMaxRetryCount e+ threadDelay delay+ retryOnException exceptionPred logger maxDelay newBaseDelay newMaxRetryCount newRng action++ | otherwise -> do+ liftIO $ do+ logWarning logger $ "Retries exhausted - " <> makeLogMsgComponentsText (Left baseDelay) maxRetryCount e+ throwIO e++ Right b -> pure b+ where+ -- e.g. delay: 1010102, maximumDelay: 12010, maxRetryCount: 9, exception: SQLError { ... }+ makeLogMsgComponentsText delay newMaxRetryCount e =+ let+ logMsgComponents =+ [ either+ (("base delay: " <>) . T.pack . show)+ (("delay: " <>) . T.pack . show)+ delay+ , "maximumDelay: " <> T.pack (show maxDelay)+ , "maxRetryCount: " <> T.pack (show newMaxRetryCount)+ , "exception: " <> T.pack (show e)]+ in+ T.intercalate ", " logMsgComponents++-- | in microseconds+oneSecond :: Int+oneSecond = 1000000++-- | in microseconds+oneMillisecond :: Int+oneMillisecond = 1000++-- | default maximum number of times to retry hiedb call+maxRetryCount :: Int+maxRetryCount = 10++retryOnSqliteBusy :: (MonadIO m, MonadCatch m, RandomGen g)+ => Logger -> g -> m a -> m a+retryOnSqliteBusy logger rng action =+ let isErrorBusy e+ | SQLError{ sqlError = ErrorBusy } <- e = Just e+ | otherwise = Nothing+ in+ retryOnException isErrorBusy logger oneSecond oneMillisecond maxRetryCount rng action++makeWithHieDbRetryable :: RandomGen g => Logger -> g -> HieDb -> WithHieDb+makeWithHieDbRetryable logger rng hieDb f =+ retryOnSqliteBusy logger rng (f hieDb)+ -- | Wraps `withHieDb` to provide a database connection for reading, and a `HieWriterChan` for -- writing. Actions are picked off one by one from the `HieWriterChan` and executed in serial -- by a worker thread using a dedicated database connection. -- This is done in order to serialize writes to the database, or else SQLite becomes unhappy-runWithDb :: Logger -> FilePath -> (HieDb -> IndexQueue -> IO ()) -> IO ()+runWithDb :: Logger -> FilePath -> (WithHieDb -> IndexQueue -> IO ()) -> IO () runWithDb logger fp k = do+ -- use non-deterministic seed because maybe multiple HLS start at same time+ -- and send bursts of requests+ rng <- Random.newStdGen -- Delete the database if it has an incompatible schema version- withHieDb fp (const $ pure ())- `Safe.catch` \IncompatibleSchemaVersion{} -> removeFile fp+ retryOnSqliteBusy+ logger+ rng+ (withHieDb fp (const $ pure ()) `Safe.catch` \IncompatibleSchemaVersion{} -> removeFile fp)+ withHieDb fp $ \writedb -> do- initConn writedb+ -- the type signature is necessary to avoid concretizing the tyvar+ -- e.g. `withWriteDbRetrable initConn` without type signature will+ -- instantiate tyvar `a` to `()`+ let withWriteDbRetryable :: WithHieDb+ withWriteDbRetryable = makeWithHieDbRetryable logger rng writedb+ withWriteDbRetryable initConn+ chan <- newTQueueIO- withAsync (writerThread writedb chan) $ \_ -> do- withHieDb fp (flip k chan)++ withAsync (writerThread withWriteDbRetryable chan) $ \_ -> do+ withHieDb fp (\readDb -> k (makeWithHieDbRetryable logger rng readDb) chan) where- writerThread db chan = do+ writerThread :: WithHieDb -> IndexQueue -> IO ()+ writerThread withHieDbRetryable chan = do -- Clear the index of any files that might have been deleted since the last run- deleteMissingRealFiles db- _ <- garbageCollectTypeNames db+ _ <- withHieDbRetryable deleteMissingRealFiles+ _ <- withHieDbRetryable garbageCollectTypeNames forever $ do k <- atomically $ readTQueue chan- k db+ k withHieDbRetryable `Safe.catch` \e@SQLError{} -> do logDebug logger $ T.pack $ "SQLite error in worker, ignoring: " ++ show e `Safe.catchAny` \e -> do@@ -245,9 +342,10 @@ return $ do extras@ShakeExtras{logger, restartShakeSession, ideNc, knownTargetsVar, lspEnv } <- getShakeExtras- let invalidateShakeCache = do+ let invalidateShakeCache :: IO ()+ invalidateShakeCache = do void $ modifyVar' version succ- recordDirtyKeys extras GhcSessionIO [emptyFilePath]+ join $ atomically $ recordDirtyKeys extras GhcSessionIO [emptyFilePath] IdeOptions{ optTesting = IdeTesting optTesting , optCheckProject = getCheckProject@@ -264,13 +362,17 @@ TargetModule _ -> do found <- filterM (IO.doesFileExist . fromNormalizedFilePath) targetLocations return (targetTarget, found)- recordDirtyKeys extras GetKnownTargets [emptyFilePath]- modifyVarIO' knownTargetsVar $ traverseHashed $ \known -> do- let known' = HM.unionWith (<>) known $ HM.fromList $ map (second Set.fromList) knownTargets- when (known /= known') $+ hasUpdate <- join $ atomically $ do+ known <- readTVar knownTargetsVar+ let known' = flip mapHashed known $ \k ->+ HM.unionWith (<>) k $ HM.fromList $ map (second Set.fromList) knownTargets+ hasUpdate = if known /= known' then Just (unhashed known') else Nothing+ writeTVar knownTargetsVar known'+ logDirtyKeys <- recordDirtyKeys extras GetKnownTargets [emptyFilePath]+ return (logDirtyKeys >> pure hasUpdate)+ for_ hasUpdate $ \x -> logDebug logger $ "Known files updated: " <>- T.pack(show $ (HM.map . Set.map) fromNormalizedFilePath known')- pure known'+ T.pack(show $ (HM.map . Set.map) fromNormalizedFilePath x) -- Create a new HscEnv from a hieYaml root and a set of options -- If the hieYaml file already has an HscEnv, the new component is@@ -330,7 +432,7 @@ newHscEnv <- -- Add the options for the current component to the HscEnv evalGhcEnv hscEnv $ do- _ <- setSessionDynFlags df+ _ <- setSessionDynFlags $ setHomeUnitId_ fakeUid df getSession -- Modify the map so the hieYaml now maps to the newly created@@ -404,7 +506,7 @@ -- update exports map extras <- getShakeExtras let !exportsMap' = createExportsMap $ mapMaybe (fmap hirModIface) modIfaces- liftIO $ modifyVar_ (exportsMap extras) $ evaluate . (exportsMap' <>)+ liftIO $ atomically $ modifyTVar' (exportsMap extras) (exportsMap' <>) return (second Map.keys res) @@ -461,8 +563,8 @@ let sessionOpts :: (Maybe FilePath, FilePath) -> IO (IdeResult HscEnvEq, [FilePath]) sessionOpts (hieYaml, file) = do- v <- fromMaybe HM.empty . Map.lookup hieYaml <$> readVar fileToFlags- cfp <- canonicalizePath file+ v <- Map.findWithDefault HM.empty hieYaml <$> readVar fileToFlags+ cfp <- makeAbsolute file case HM.lookup (toNormalizedFilePath' cfp) v of Just (opts, old_di) -> do deps_ok <- checkDependencyInfo old_di@@ -483,7 +585,7 @@ -- before attempting to do so. let getOptions :: FilePath -> IO (IdeResult HscEnvEq, [FilePath]) getOptions file = do- ncfp <- toNormalizedFilePath' <$> canonicalizePath file+ ncfp <- toNormalizedFilePath' <$> makeAbsolute file cachedHieYamlLocation <- HM.lookup ncfp <$> readVar filesMap hieYaml <- cradleLoc file sessionOpts (join cachedHieYamlLocation <|> hieYaml, file) `Safe.catch` \e ->@@ -526,10 +628,7 @@ emptyHscEnv :: IORef NameCache -> FilePath -> IO HscEnv emptyHscEnv nc libDir = do env <- runGhc (Just libDir) getSession- when (Compat.ghcVersion < Compat.GHC90) $- -- This causes ghc9 to crash with the error:- -- Couldn't find a target code interpreter. Try with -fexternal-interpreter- initDynLinker env+ initDynLinker env pure $ setNameCache nc (hscSetFlags ((hsc_dflags env){useUnicode = True }) env) data TargetDetails = TargetDetails@@ -553,11 +652,11 @@ , i <- is , boot <- ["", "-boot"] ]- locs <- mapM (fmap toNormalizedFilePath' . canonicalizePath) fps+ locs <- mapM (fmap toNormalizedFilePath' . makeAbsolute) fps return [TargetDetails (TargetModule mod) env dep locs] -- For a 'TargetFile' we consider all the possible module names fromTargetId _ _ (GHC.TargetFile f _) env deps = do- nf <- toNormalizedFilePath' <$> canonicalizePath f+ nf <- toNormalizedFilePath' <$> makeAbsolute f return [TargetDetails (TargetFile nf) env deps [nf]] toFlagsMap :: TargetDetails -> [(NormalizedFilePath, (IdeResult HscEnvEq, DependencyInfo))]
src/Control/Concurrent/Strict.hs view
@@ -4,25 +4,26 @@ ,module Control.Concurrent.Extra ) where -import Control.Concurrent.Extra hiding (modifyVar, modifyVar_)+import Control.Concurrent.Extra hiding (modifyVar, modifyVar',+ modifyVar_) import qualified Control.Concurrent.Extra as Extra import Control.Exception (evaluate) import Control.Monad (void) import Data.Tuple.Extra (dupe) -- | Strict modification that returns the new value-modifyVar' :: Var a -> (a -> a) -> IO a+modifyVar' :: Extra.Var a -> (a -> a) -> IO a modifyVar' var upd = modifyVarIO' var (pure . upd) -- | Strict modification that returns the new value-modifyVarIO' :: Var a -> (a -> IO a) -> IO a+modifyVarIO' :: Extra.Var a -> (a -> IO a) -> IO a modifyVarIO' var upd = do res <- Extra.modifyVar var $ \v -> do v' <- upd v pure $ dupe v' evaluate res -modifyVar :: Var a -> (a -> IO (a, b)) -> IO b+modifyVar :: Extra.Var a -> (a -> IO (a, b)) -> IO b modifyVar var upd = do (new, res) <- Extra.modifyVar var $ \old -> do (new,res) <- upd old@@ -30,5 +31,5 @@ void $ evaluate new return res -modifyVar_ :: Var a -> (a -> IO a) -> IO ()+modifyVar_ :: Extra.Var a -> (a -> IO a) -> IO () modifyVar_ var upd = void $ modifyVarIO' var upd
src/Development/IDE/Core/Actions.hs view
@@ -1,5 +1,5 @@-{-# LANGUAGE NoApplicativeDo #-}-{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-} module Development.IDE.Core.Actions ( getAtPoint , getDefinition@@ -84,24 +84,20 @@ -- | Goto Definition. getDefinition :: NormalizedFilePath -> Position -> IdeAction (Maybe [Location]) getDefinition file pos = runMaybeT $ do- ide <- ask+ ide@ShakeExtras{ withHieDb, hiedbWriter } <- ask opts <- liftIO $ getIdeOptionsIO ide (HAR _ hf _ _ _, mapping) <- useE GetHieAst file (ImportMap imports, _) <- useE GetImportMap file !pos' <- MaybeT (pure $ fromCurrentPosition mapping pos)- hiedb <- lift $ asks hiedb- dbWriter <- lift $ asks hiedbWriter- toCurrentLocations mapping <$> AtPoint.gotoDefinition hiedb (lookupMod dbWriter) opts imports hf pos'+ toCurrentLocations mapping <$> AtPoint.gotoDefinition withHieDb (lookupMod hiedbWriter) opts imports hf pos' getTypeDefinition :: NormalizedFilePath -> Position -> IdeAction (Maybe [Location]) getTypeDefinition file pos = runMaybeT $ do- ide <- ask+ ide@ShakeExtras{ withHieDb, hiedbWriter } <- ask opts <- liftIO $ getIdeOptionsIO ide (hf, mapping) <- useE GetHieAst file !pos' <- MaybeT (return $ fromCurrentPosition mapping pos)- hiedb <- lift $ asks hiedb- dbWriter <- lift $ asks hiedbWriter- toCurrentLocations mapping <$> AtPoint.gotoTypeDefinition hiedb (lookupMod dbWriter) opts hf pos'+ toCurrentLocations mapping <$> AtPoint.gotoTypeDefinition withHieDb (lookupMod hiedbWriter) opts hf pos' highlightAtPoint :: NormalizedFilePath -> Position -> IdeAction (Maybe [DocumentHighlight]) highlightAtPoint file pos = runMaybeT $ do@@ -113,13 +109,13 @@ -- Refs are not an IDE action, so it is OK to be slow and (more) accurate refsAtPoint :: NormalizedFilePath -> Position -> Action [Location] refsAtPoint file pos = do- ShakeExtras{hiedb} <- getShakeExtras+ ShakeExtras{withHieDb} <- getShakeExtras fs <- HM.keys <$> getFilesOfInterestUntracked asts <- HM.fromList . mapMaybe sequence . zip fs <$> usesWithStale GetHieAst fs- AtPoint.referencesAtPoint hiedb file pos (AtPoint.FOIReferences asts)+ AtPoint.referencesAtPoint withHieDb file pos (AtPoint.FOIReferences asts) workspaceSymbols :: T.Text -> IdeAction (Maybe [SymbolInformation]) workspaceSymbols query = runMaybeT $ do- hiedb <- lift $ asks hiedb- res <- liftIO $ HieDb.searchDef hiedb $ T.unpack query+ ShakeExtras{withHieDb} <- ask+ res <- liftIO $ withHieDb (\hieDb -> HieDb.searchDef hieDb $ T.unpack query) pure $ mapMaybe AtPoint.defRowToSymbolInfo res
src/Development/IDE/Core/Compile.hs view
@@ -27,7 +27,6 @@ , loadHieFile , loadInterface , loadModulesHome- , setupFinderCache , getDocsBatch , lookupName ,mergeEnvs) where@@ -56,9 +55,9 @@ import Language.LSP.Types (DiagnosticTag (..)) #if MIN_VERSION_ghc(8,10,0)-import Control.DeepSeq (force, rnf)+import Control.DeepSeq (force, liftRnf, rnf, rwhnf) #else-import Control.DeepSeq (rnf)+import Control.DeepSeq (liftRnf, rnf, rwhnf) import ErrUtils #endif @@ -69,6 +68,10 @@ import TcSplice #endif +#if MIN_VERSION_ghc(9,2,0)+import qualified GHC.Types.Error as Error+#endif+ import Control.Exception (evaluate) import Control.Exception.Safe import Control.Lens hiding (List)@@ -79,6 +82,7 @@ import qualified Data.ByteString as BS import qualified Data.DList as DL import Data.IORef+import qualified Data.IntMap.Strict as IntMap import Data.List.Extra import qualified Data.Map.Strict as Map import Data.Maybe@@ -96,12 +100,13 @@ parsedSource) import Control.Concurrent.Extra-import Control.Concurrent.STM hiding (orElse)+import Control.Concurrent.STM.Stats hiding (orElse) import Data.Aeson (toJSON) import Data.Binary import Data.Coerce import Data.Functor import qualified Data.HashMap.Strict as HashMap+import Data.IntMap (IntMap) import Data.Map (Map) import Data.Tuple.Extra (dupe) import Data.Unique as Unique@@ -142,24 +147,27 @@ -> ParsedModule -> IO (IdeResult TcModuleResult) typecheckModule (IdeDefer defer) hsc keep_lbls pm = do- fmap (either (,Nothing) id) $- catchSrcErrors (hsc_dflags hsc) "typecheck" $ do- let modSummary = pm_mod_summary pm dflags = ms_hspp_opts modSummary-- modSummary' <- initPlugins hsc modSummary- (warnings, tcm) <- withWarnings "typecheck" $ \tweak ->- let- session = tweak (hscSetFlags dflags hsc)- -- TODO: maybe settings ms_hspp_opts is unnecessary?- mod_summary'' = modSummary' { ms_hspp_opts = hsc_dflags session}- in- tcRnModule hsc keep_lbls $ demoteIfDefer pm{pm_mod_summary = mod_summary''}- let errorPipeline = unDefer . hideDiag dflags . tagDiag- diags = map errorPipeline warnings- deferedError = any fst diags- return (map snd diags, Just $ tcm{tmrDeferedError = deferedError})+ mmodSummary' <- catchSrcErrors (hsc_dflags hsc) "typecheck (initialize plugins)"+ (initPlugins hsc modSummary)+ case mmodSummary' of+ Left errs -> return (errs, Nothing)+ Right modSummary' -> do+ (warnings, etcm) <- withWarnings "typecheck" $ \tweak ->+ let+ session = tweak (hscSetFlags dflags hsc)+ -- TODO: maybe settings ms_hspp_opts is unnecessary?+ mod_summary'' = modSummary' { ms_hspp_opts = hsc_dflags session}+ in+ catchSrcErrors (hsc_dflags hsc) "typecheck" $ do+ tcRnModule session keep_lbls $ demoteIfDefer pm{pm_mod_summary = mod_summary''}+ let errorPipeline = unDefer . hideDiag dflags . tagDiag+ diags = map errorPipeline warnings+ deferedError = any fst diags+ case etcm of+ Left errs -> return (map snd diags ++ errs, Nothing)+ Right tcm -> return (map snd diags, Just $ tcm{tmrDeferedError = deferedError}) where demoteIfDefer = if defer then demoteTypeErrorsToWarnings else id @@ -208,7 +216,7 @@ unload hsc_env_tmp keep_lbls ((tc_gbl_env, mrn_info), splices)- <- liftIO $ captureSplices (hscSetFlags (ms_hspp_opts ms) hsc_env) $ \hsc_env_tmp ->+ <- liftIO $ captureSplices hsc_env_tmp $ \hsc_env_tmp -> do hscTypecheckRename hsc_env_tmp ms $ HsParsedModule { hpm_module = parsedSource pmod, hpm_src_files = pm_extra_src_files pmod,@@ -305,6 +313,8 @@ (warnings,desugared_guts) <- withWarnings "compile" $ \tweak -> do let session' = tweak (hscSetFlags (ms_hspp_opts ms) session) -- TODO: maybe settings ms_hspp_opts is unnecessary?+ -- MP: the flags in ModSummary should be right, if they are wrong then+ -- the correct place to fix this is when the ModSummary is created. desugar <- hscDesugar session' (ms { ms_hspp_opts = hsc_dflags session' }) tcg if simplify then do@@ -325,7 +335,7 @@ withWarnings "object" $ \tweak -> do let env' = tweak (hscSetFlags (ms_hspp_opts summary) session) target = platformDefaultBackend (hsc_dflags env')- newFlags = setBackend target $ updOptLevel 0 $ (hsc_dflags env') { outputFile = Just dot_o }+ newFlags = setBackend target $ updOptLevel 0 $ setOutputFile dot_o $ hsc_dflags env' session' = hscSetFlags newFlags session #if MIN_VERSION_ghc(9,0,1) (outputFilename, _mStub, _foreign_files, _cinfos) <- hscGenHardCode session' guts@@ -465,12 +475,19 @@ top_ev_binds = tcg_ev_binds ts :: Util.Bag EvBind insts = tcg_insts ts :: [ClsInst] tcs = tcg_tcs ts :: [TyCon]- Just <$> GHC.enrichHie (fake_splice_binds `Util.unionBags` real_binds) (tmrRenamed tcm) top_ev_binds insts tcs+ run ts $+ Just <$> GHC.enrichHie (fake_splice_binds `Util.unionBags` real_binds) (tmrRenamed tcm) top_ev_binds insts tcs #else Just <$> GHC.enrichHie (fake_splice_binds `Util.unionBags` real_binds) (tmrRenamed tcm) #endif where dflags = hsc_dflags hscEnv+ run ts =+#if MIN_VERSION_ghc(9,2,0)+ fmap (join . snd) . liftIO . initDs hscEnv ts+#else+ id+#endif spliceExpresions :: Splices -> [LHsExpr GhcTc] spliceExpresions Splices{..} =@@ -521,7 +538,7 @@ -- 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- writeTQueue indexQueue $ \db -> do+ 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@@ -532,7 +549,7 @@ Just pendingHash -> pendingHash /= hash unless newerScheduled $ do pre optProgressStyle- addRefsFromLoaded db targetPath (RealFile $ fromNormalizedFilePath srcPath) hash hf'+ withHieDb (\db -> HieDb.addRefsFromLoaded db targetPath (HieDb.RealFile $ fromNormalizedFilePath srcPath) hash hf') post where mod_location = ms_location mod_summary@@ -564,6 +581,11 @@ done <- readTVar indexCompleted remaining <- HashMap.size <$> readTVar indexPending pure (done, remaining)+ let+ progressFrac :: Double+ progressFrac = fromIntegral done / fromIntegral (done + remaining)+ progressPct :: LSP.UInt+ progressPct = floor $ 100 * progressFrac whenJust (lspEnv se) $ \env -> whenJust tok $ \tok -> LSP.runLspT env $ LSP.sendNotification LSP.SProgress $ LSP.ProgressParams tok $@@ -572,7 +594,7 @@ Percentage -> LSP.WorkDoneProgressReportParams { _cancellable = Nothing , _message = Nothing- , _percentage = Just (100 * fromIntegral done / fromIntegral (done + remaining) )+ , _percentage = Just progressPct } Explicit -> LSP.WorkDoneProgressReportParams { _cancellable = Nothing@@ -650,30 +672,6 @@ . (("Error during " ++ T.unpack source) ++) . show @SomeException ] --- | Initialise the finder cache, dependencies should be topologically--- sorted.-setupFinderCache :: [ModSummary] -> HscEnv -> IO HscEnv-setupFinderCache mss session = do-- -- Make modules available for others that import them,- -- by putting them in the finder cache.- let ims = map (installedModule (homeUnitId_ $ hsc_dflags session) . moduleName . ms_mod) mss- ifrs = zipWith (\ms -> InstalledFound (ms_location ms)) mss ims- -- set the target and module graph in the session- graph = mkModuleGraph mss-- -- We have to create a new IORef here instead of modifying the existing IORef as- -- it is shared between concurrent compilations.- prevFinderCache <- readIORef $ hsc_FC session- let newFinderCache =- foldl'- (\fc (im, ifr) -> GHC.extendInstalledModuleEnv fc im ifr) prevFinderCache- $ zip ims ifrs- newFinderCacheVar <- newIORef $! newFinderCache-- pure $ session { hsc_FC = newFinderCacheVar, hsc_mod_graph = graph }-- -- | Load modules, quickly. Input doesn't need to be desugared. -- A module must be loaded before dependent modules can be typechecked. -- This variant of loadModuleHome will *never* cause recompilation, it just@@ -686,7 +684,8 @@ -> HscEnv -> HscEnv loadModulesHome mod_infos e =- e { hsc_HPT = addListToHpt (hsc_HPT e) [(mod_name x, x) | x <- mod_infos]+ let !new_modules = addListToHpt (hsc_HPT e) [(mod_name x, x) | x <- mod_infos]+ in e { hsc_HPT = new_modules , hsc_type_env_var = Nothing } where mod_name = moduleName . mi_module . hm_iface@@ -695,17 +694,29 @@ mergeEnvs :: HscEnv -> [ModSummary] -> [HomeModInfo] -> [HscEnv] -> IO HscEnv mergeEnvs env extraModSummaries extraMods envs = do prevFinderCache <- concatFC <$> mapM (readIORef . hsc_FC) envs- let ims = map (Compat.installedModule (homeUnitId_ $ hsc_dflags env) . moduleName . ms_mod) extraModSummaries+ let ims = map (\ms -> Compat.installedModule (toUnitId $ moduleUnit $ ms_mod ms) (moduleName (ms_mod ms))) extraModSummaries ifrs = zipWith (\ms -> InstalledFound (ms_location ms)) extraModSummaries ims+ -- Very important to force this as otherwise the hsc_mod_graph field is not+ -- forced and ends up retaining a reference to all the old hsc_envs we have merged to get+ -- this new one, which in turn leads to the EPS referencing the HPT.+ module_graph_nodes =+#if MIN_VERSION_ghc(9,2,0)+ -- We don't do any instantiation for backpack at this point of time, so it is OK to use+ -- 'extendModSummaryNoDeps'.+ -- This may have to change in the future.+ map extendModSummaryNoDeps $+#endif+ extraModSummaries ++ nubOrdOn ms_mod (concatMap (mgModSummaries . hsc_mod_graph) envs)+ newFinderCache <- newIORef $ foldl' (\fc (im, ifr) -> Compat.extendInstalledModuleEnv fc im ifr) prevFinderCache $ zip ims ifrs- return $ loadModulesHome extraMods $ env{+ liftRnf rwhnf module_graph_nodes `seq` (return $ loadModulesHome extraMods $ env{ hsc_HPT = foldMapBy mergeUDFM emptyUDFM hsc_HPT envs, hsc_FC = newFinderCache,- hsc_mod_graph = mkModuleGraph $ extraModSummaries ++ nubOrdOn ms_mod (concatMap (mgModSummaries . hsc_mod_graph) envs)- }+ hsc_mod_graph = mkModuleGraph module_graph_nodes+ }) where mergeUDFM = plusUDFM_C combineModules combineModules a b@@ -753,8 +764,9 @@ implicit_prelude = xopt LangExt.ImplicitPrelude dflags implicit_imports = mkPrelImports mod main_loc implicit_prelude imps+ convImport (L _ i) = (fmap sl_fs (ideclPkgQual i)- , ideclName i)+ , reLoc $ ideclName i) srcImports = map convImport src_idecls textualImports = map convImport (implicit_imports ++ ordinary_imps)@@ -824,15 +836,11 @@ parseHeader dflags filename contents = do let loc = mkRealSrcLoc (Util.mkFastString filename) 1 1 case unP Compat.parseHeader (initParserState (initParserOpts dflags) contents loc) of-#if MIN_VERSION_ghc(8,10,0)- PFailed pst ->- throwE $ diagFromErrMsgs "parser" dflags $ getErrorMessages pst dflags-#else- PFailed _ locErr msgErr ->- throwE $ diagFromErrMsg "parser" dflags $ mkPlainErrMsg dflags locErr msgErr-#endif+ PFailedWithErrorMessages msgs ->+ throwE $ diagFromErrMsgs "parser" dflags $ msgs dflags POk pst rdr_module -> do- let (warns, errs) = getMessages pst dflags+ let (warns, errs) = getMessages' pst dflags+ -- Just because we got a `POk`, it doesn't mean there -- weren't errors! To clarify, the GHC parser -- distinguishes between fatal and non-fatal@@ -843,9 +851,9 @@ -- errors are those from which a parse tree just can't -- be produced. unless (null errs) $- throwE $ diagFromErrMsgs "parser" dflags (fmap pprError errs)+ throwE $ diagFromErrMsgs "parser" dflags errs - let warnings = diagFromErrMsgs "parser" dflags (fmap pprWarning warns)+ let warnings = diagFromErrMsgs "parser" dflags warns return (warnings, rdr_module) -- | Given a buffer, flags, and file path, produce a@@ -862,16 +870,11 @@ dflags = ms_hspp_opts ms contents = fromJust $ ms_hspp_buf ms case unP Compat.parseModule (initParserState (initParserOpts dflags) contents loc) of-#if MIN_VERSION_ghc(8,10,0)- PFailed pst -> throwE $ diagFromErrMsgs "parser" dflags $ getErrorMessages pst dflags-#else- PFailed _ locErr msgErr ->- throwE $ diagFromErrMsg "parser" dflags $ mkPlainErrMsg dflags locErr msgErr-#endif+ PFailedWithErrorMessages msgs -> throwE $ diagFromErrMsgs "parser" dflags $ msgs dflags POk pst rdr_module -> let hpm_annotations = mkApiAnns pst- (warns, errs) = getMessages pst dflags+ (warns, errs) = getMessages' pst dflags in do -- Just because we got a `POk`, it doesn't mean there@@ -921,7 +924,7 @@ -- filter them out: srcs2 <- liftIO $ filterM doesFileExist srcs1 - let pm = mkParsedModule ms parsed' srcs2 hpm_annotations+ let pm = ParsedModule ms parsed' srcs2 hpm_annotations warnings = diagFromErrMsgs "parser" dflags warns pure (warnings ++ preproc_warnings, pm) @@ -998,9 +1001,9 @@ :: HscEnv -> Module -- ^ a moudle where the names are in scope -> [Name]- -> IO [Either String (Maybe HsDocString, Map.Map Int HsDocString)]+ -> IO [Either String (Maybe HsDocString, IntMap HsDocString)] getDocsBatch hsc_env _mod _names = do- ((_warns,errs), res) <- initTc hsc_env HsSrcFile False _mod fakeSpan $ forM _names $ \name ->+ (msgs, res) <- initTc hsc_env HsSrcFile False _mod fakeSpan $ forM _names $ \name -> case nameModule_maybe name of Nothing -> return (Left $ NameHasNoModule name) Just mod -> do@@ -1008,13 +1011,21 @@ , mi_decl_docs = DeclDocMap dmap , mi_arg_docs = ArgDocMap amap } <- loadModuleInterface "getModuleInterface" mod- if isNothing mb_doc_hdr && Map.null dmap && Map.null amap+ if isNothing mb_doc_hdr && Map.null dmap && null amap then pure (Left (NoDocsInIface mod $ compiled name))- else pure (Right ( Map.lookup name dmap- , Map.findWithDefault Map.empty name amap))+ else pure (Right ( Map.lookup name dmap ,+#if !MIN_VERSION_ghc(9,2,0)+ IntMap.fromAscList $ Map.toAscList $+#endif+ Map.findWithDefault mempty name amap)) case res of Just x -> return $ map (first $ T.unpack . showGhc) x- Nothing -> throwErrors errs+ Nothing -> throwErrors+#if MIN_VERSION_ghc(9,2,0)+ $ Error.getErrorMessages msgs+#else+ $ snd msgs+#endif where throwErrors = liftIO . throwIO . mkSrcErr compiled n =
src/Development/IDE/Core/Debouncer.hs view
@@ -9,13 +9,13 @@ ) where import Control.Concurrent.Async-import Control.Concurrent.Strict+import Control.Concurrent.STM.Stats (atomically, atomicallyNamed) import Control.Exception-import Control.Monad (join)-import Data.Foldable (traverse_)-import Data.HashMap.Strict (HashMap)-import qualified Data.HashMap.Strict as Map+import Control.Monad (join)+import Data.Foldable (traverse_) import Data.Hashable+import qualified Focus+import qualified StmContainers.Map as STM import System.Time.Extra -- | A debouncer can be used to avoid triggering many events@@ -31,7 +31,7 @@ -- | Debouncer used in the IDE that delays events as expected. newAsyncDebouncer :: (Eq k, Hashable k) => IO (Debouncer k)-newAsyncDebouncer = Debouncer . asyncRegisterEvent <$> newVar Map.empty+newAsyncDebouncer = Debouncer . asyncRegisterEvent <$> STM.newIO -- | Register an event that will fire after the given delay if no other event -- for the same key gets registered until then.@@ -39,20 +39,20 @@ -- If there is a pending event for the same key, the pending event will be killed. -- Events are run unmasked so it is up to the user of `registerEvent` -- to mask if required.-asyncRegisterEvent :: (Eq k, Hashable k) => Var (HashMap k (Async ())) -> Seconds -> k -> IO () -> IO ()+asyncRegisterEvent :: (Eq k, Hashable k) => STM.Map k (Async ()) -> Seconds -> k -> IO () -> IO () asyncRegisterEvent d 0 k fire = do- join $ modifyVar d $ \m -> do- (cancel, !m') <- evaluate $ Map.alterF (\prev -> (traverse_ cancel prev, Nothing)) k m- return (m', cancel)+ join $ atomically $ do+ prev <- STM.focus Focus.lookupAndDelete k d+ return $ traverse_ cancel prev fire asyncRegisterEvent d delay k fire = mask_ $ do a <- asyncWithUnmask $ \unmask -> unmask $ do sleep delay fire- modifyVar_ d (evaluate . Map.delete k)- join $ modifyVar d $ \m -> do- (cancel, !m') <- evaluate $ Map.alterF (\prev -> (traverse_ cancel prev, Just a)) k m- return (m', cancel)+ atomically $ STM.delete k d+ do+ prev <- atomicallyNamed "debouncer" $ STM.focus (Focus.lookup <* Focus.insert a) k d+ traverse_ cancel prev -- | Debouncer used in the DAML CLI compiler that emits events immediately. noopDebouncer :: Debouncer k
src/Development/IDE/Core/FileExists.hs view
@@ -10,13 +10,12 @@ ) where -import Control.Concurrent.Strict+import Control.Concurrent.STM.Stats (atomically,+ atomicallyNamed) import Control.Exception import Control.Monad.Extra import Control.Monad.IO.Class import qualified Data.ByteString as BS-import Data.HashMap.Strict (HashMap)-import qualified Data.HashMap.Strict as HashMap import Data.List (partition) import Data.Maybe import Development.IDE.Core.FileStore@@ -26,9 +25,11 @@ import Development.IDE.Graph import Development.IDE.Types.Location import Development.IDE.Types.Options+import qualified Focus import Ide.Plugin.Config (Config) import Language.LSP.Server hiding (getVirtualFile) import Language.LSP.Types+import qualified StmContainers.Map as STM import qualified System.Directory as Dir import qualified System.FilePath.Glob as Glob @@ -74,10 +75,10 @@ -- | A map for tracking the file existence. -- If a path maps to 'True' then it exists; if it maps to 'False' then it doesn't exist'; and -- if it's not in the map then we don't know.-type FileExistsMap = (HashMap NormalizedFilePath Bool)+type FileExistsMap = STM.Map NormalizedFilePath Bool -- | A wrapper around a mutable 'FileExistsState'-newtype FileExistsMapVar = FileExistsMapVar (Var FileExistsMap)+newtype FileExistsMapVar = FileExistsMapVar FileExistsMap instance IsIdeGlobal FileExistsMapVar @@ -85,24 +86,27 @@ getFileExistsMapUntracked :: Action FileExistsMap getFileExistsMapUntracked = do FileExistsMapVar v <- getIdeGlobalAction- liftIO $ readVar v+ return v -- | Modify the global store of file exists. modifyFileExists :: IdeState -> [(NormalizedFilePath, FileChangeType)] -> IO () modifyFileExists state changes = do FileExistsMapVar var <- getIdeGlobalState state- changesMap <- evaluate $ HashMap.fromList changes -- Masked to ensure that the previous values are flushed together with the map update- mask $ \_ -> do -- update the map- void $ modifyVar' var $ HashMap.union (HashMap.mapMaybe fromChange changesMap)+ mask_ $ join $ atomicallyNamed "modifyFileExists" $ do+ forM_ changes $ \(f,c) ->+ case fromChange c of+ Just c' -> STM.focus (Focus.insert c') f var+ Nothing -> pure () -- See Note [Invalidating file existence results] -- flush previous values let (fileModifChanges, fileExistChanges) =- partition ((== FcChanged) . snd) (HashMap.toList changesMap)+ partition ((== FcChanged) . snd) changes mapM_ (deleteValue (shakeExtras state) GetFileExists . fst) fileExistChanges- recordDirtyKeys (shakeExtras state) GetFileExists $ map fst fileExistChanges- recordDirtyKeys (shakeExtras state) GetModificationTime $ map fst fileModifChanges+ io1 <- recordDirtyKeys (shakeExtras state) GetFileExists $ map fst fileExistChanges+ io2 <- recordDirtyKeys (shakeExtras state) GetModificationTime $ map fst fileModifChanges+ return (io1 <> io2) fromChange :: FileChangeType -> Maybe Bool fromChange FcCreated = Just True@@ -161,7 +165,7 @@ -- Create the global always, although it should only be used if we have fast rules. -- But there's a chance someone will send unexpected notifications anyway, -- e.g. https://github.com/haskell/ghcide/issues/599- addIdeGlobal . FileExistsMapVar =<< liftIO (newVar [])+ addIdeGlobal . FileExistsMapVar =<< liftIO STM.newIO extras <- getShakeExtrasRules opts <- liftIO $ getIdeOptionsIO extras@@ -210,7 +214,7 @@ -- Could in principle use 'alwaysRerun' here, but it's too slwo, See Note [Invalidating file existence results] mp <- getFileExistsMapUntracked - let mbFilesWatched = HashMap.lookup file mp+ mbFilesWatched <- liftIO $ atomically $ STM.lookup file mp exist <- case mbFilesWatched of Just exist -> pure exist -- We don't know about it: use the slow route.
src/Development/IDE/Core/FileStore.hs view
@@ -24,7 +24,8 @@ registerFileWatches ) where -import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.Stats (STM, atomically,+ modifyTVar') import Control.Concurrent.STM.TQueue (writeTQueue) import Control.Concurrent.Strict import Control.Exception@@ -63,7 +64,6 @@ import qualified Data.Binary as B import qualified Data.ByteString.Lazy as LBS import qualified Data.HashSet as HSet-import Data.IORef.Extra (atomicModifyIORef_) import Data.List (foldl') import qualified Data.Text as Text import Development.IDE.Core.IdeConfiguration (isWorkspaceFile)@@ -160,7 +160,7 @@ isInterface f = takeExtension (fromNormalizedFilePath f) `elem` [".hi", ".hi-boot"] -- | Reset the GetModificationTime state of interface files-resetInterfaceStore :: ShakeExtras -> NormalizedFilePath -> IO ()+resetInterfaceStore :: ShakeExtras -> NormalizedFilePath -> STM () resetInterfaceStore state f = do deleteValue state GetModificationTime f @@ -175,7 +175,8 @@ case c of FcChanged -- already checked elsewhere | not $ HM.member nfp fois- -> deleteValue (shakeExtras ideState) GetModificationTime nfp+ -> atomically $+ deleteValue (shakeExtras ideState) GetModificationTime nfp _ -> pure () @@ -262,7 +263,7 @@ VFSHandle{..} <- getIdeGlobalState state when (isJust setVirtualFileContents) $ fail "setFileModified can't be called on this type of VFSHandle"- recordDirtyKeys (shakeExtras state) GetModificationTime [nfp]+ join $ atomically $ recordDirtyKeys (shakeExtras state) GetModificationTime [nfp] restartShakeSession (shakeExtras state) (fromNormalizedFilePath nfp ++ " (modified)") [] when checkParents $ typecheckParents state nfp@@ -292,9 +293,10 @@ when (isJust setVirtualFileContents) $ fail "setSomethingModified can't be called on this type of VFSHandle" -- Update database to remove any files that might have been renamed/deleted- atomically $ writeTQueue (indexQueue $ hiedbWriter $ shakeExtras state) deleteMissingRealFiles- atomicModifyIORef_ (dirtyKeys $ shakeExtras state) $ \x ->- foldl' (flip HSet.insert) x keys+ atomically $ do+ writeTQueue (indexQueue $ hiedbWriter $ shakeExtras state) (\withHieDb -> withHieDb deleteMissingRealFiles)+ modifyTVar' (dirtyKeys $ shakeExtras state) $ \x ->+ foldl' (flip HSet.insert) x keys void $ restartShakeSession (shakeExtras state) reason [] registerFileWatches :: [String] -> LSP.LspT Config IO Bool
src/Development/IDE/Core/OfInterest.hs view
@@ -20,19 +20,26 @@ import Control.Concurrent.Strict import Control.Monad import Control.Monad.IO.Class-import Data.HashMap.Strict (HashMap)-import qualified Data.HashMap.Strict as HashMap-import qualified Data.Text as T+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HashMap+import qualified Data.Text as T import Development.IDE.Graph -import qualified Data.ByteString as BS-import Data.Maybe (catMaybes)+import Control.Concurrent.STM.Stats (atomically,+ modifyTVar')+import Data.Aeson (toJSON)+import qualified Data.ByteString as BS+import Data.Maybe (catMaybes) import Development.IDE.Core.ProgressReporting import Development.IDE.Core.RuleTypes import Development.IDE.Core.Shake+import Development.IDE.Plugin.Completions.Types import Development.IDE.Types.Exports import Development.IDE.Types.Location import Development.IDE.Types.Logger+import Development.IDE.Types.Options (IdeTesting (..))+import qualified Language.LSP.Server as LSP+import qualified Language.LSP.Types as LSP newtype OfInterestVar = OfInterestVar (Var (HashMap NormalizedFilePath FileOfInterestStatus)) instance IsIdeGlobal OfInterestVar@@ -86,7 +93,7 @@ let (prev, new) = HashMap.alterF (, Just v) f dict pure (new, (prev, new)) when (prev /= Just v) $- recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]+ join $ atomically $ recordDirtyKeys (shakeExtras state) IsFileOfInterest [f] logDebug (ideLogger state) $ "Set files of interest to: " <> T.pack (show files) @@ -94,7 +101,7 @@ deleteFileOfInterest state f = do OfInterestVar var <- getIdeGlobalState state files <- modifyVar' var $ HashMap.delete f- recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]+ join $ atomically $ recordDirtyKeys (shakeExtras state) IsFileOfInterest [f] logDebug (ideLogger state) $ "Set files of interest to: " <> T.pack (show files) scheduleGarbageCollection :: IdeState -> IO ()@@ -107,13 +114,23 @@ kick :: Action () kick = do files <- HashMap.keys <$> getFilesOfInterestUntracked- ShakeExtras{exportsMap, progress} <- getShakeExtras+ ShakeExtras{exportsMap, ideTesting = IdeTesting testing, lspEnv, progress} <- getShakeExtras+ let signal msg = when testing $ liftIO $+ mRunLspT lspEnv $+ LSP.sendNotification (LSP.SCustomMethod msg) $+ toJSON $ map fromNormalizedFilePath files++ signal "kick/start" liftIO $ progressUpdate progress KickStarted -- Update the exports map- results <- uses GenerateCore files <* uses GetHieAst files+ results <- uses GenerateCore files+ <* uses GetHieAst files+ -- needed to have non local completions on the first edit+ -- when the first edit breaks the module header+ <* uses NonLocalCompletions files let mguts = catMaybes results- void $ liftIO $ modifyVar' exportsMap (updateExportsMapMg mguts)+ void $ liftIO $ atomically $ modifyTVar' exportsMap (updateExportsMapMg mguts) liftIO $ progressUpdate progress KickCompleted @@ -122,3 +139,5 @@ when garbageCollectionScheduled $ do void garbageCollectDirtyKeys liftIO $ writeVar var False++ signal "kick/done"
src/Development/IDE/Core/PositionMapping.hs view
@@ -31,7 +31,8 @@ import qualified Data.Text as T import qualified Data.Vector.Unboxed as V import Language.LSP.Types (Position (Position), Range (Range),- TextDocumentContentChangeEvent (TextDocumentContentChangeEvent))+ TextDocumentContentChangeEvent (TextDocumentContentChangeEvent),+ UInt) -- | Either an exact position, or the range of text that was substituted data PositionResult a@@ -140,14 +141,17 @@ where lineDiff = linesNew - linesOld linesNew = T.count "\n" t- linesOld = endLine - startLine+ linesOld = fromIntegral endLine - fromIntegral startLine+ newEndColumn :: UInt newEndColumn- | linesNew == 0 = startColumn + T.length t- | otherwise = T.length $ T.takeWhileEnd (/= '\n') t+ | linesNew == 0 = fromIntegral $ fromIntegral startColumn + T.length t+ | otherwise = fromIntegral $ T.length $ T.takeWhileEnd (/= '\n') t+ newColumn :: UInt newColumn- | line == endLine = column + newEndColumn - endColumn+ | line == endLine = fromIntegral $ (fromIntegral column + newEndColumn) - fromIntegral endColumn | otherwise = column- newLine = line + lineDiff+ newLine :: UInt+ newLine = fromIntegral $ fromIntegral line + lineDiff fromCurrent :: Range -> T.Text -> Position -> PositionResult Position fromCurrent (Range start@(Position startLine startColumn) end@(Position endLine endColumn)) t (Position line column)@@ -163,19 +167,23 @@ where lineDiff = linesNew - linesOld linesNew = T.count "\n" t- linesOld = endLine - startLine- newEndLine = endLine + lineDiff+ linesOld = fromIntegral endLine - fromIntegral startLine+ newEndLine :: UInt+ newEndLine = fromIntegral $ fromIntegral endLine + lineDiff+ newEndColumn :: UInt newEndColumn- | linesNew == 0 = startColumn + T.length t- | otherwise = T.length $ T.takeWhileEnd (/= '\n') t+ | linesNew == 0 = fromIntegral $ fromIntegral startColumn + T.length t+ | otherwise = fromIntegral $ T.length $ T.takeWhileEnd (/= '\n') t+ newColumn :: UInt newColumn- | line == newEndLine = column - (newEndColumn - endColumn)+ | line == newEndLine = fromIntegral $ (fromIntegral column + fromIntegral endColumn) - newEndColumn | otherwise = column- newLine = line - lineDiff+ newLine :: UInt+ newLine = fromIntegral $ fromIntegral line - lineDiff deltaFromDiff :: T.Text -> T.Text -> PositionDelta deltaFromDiff (T.lines -> old) (T.lines -> new) =- PositionDelta (lookupPos lnew o2nPrevs o2nNexts old2new) (lookupPos lold n2oPrevs n2oNexts new2old)+ PositionDelta (lookupPos (fromIntegral lnew) o2nPrevs o2nNexts old2new) (lookupPos (fromIntegral lold) n2oPrevs n2oNexts new2old) where !lnew = length new !lold = length old@@ -194,17 +202,16 @@ f :: Int -> Int -> Int f !a !b = if b == -1 then a else b - lookupPos :: Int -> V.Vector Int -> V.Vector Int -> V.Vector Int -> Position -> PositionResult Position+ lookupPos :: UInt -> V.Vector Int -> V.Vector Int -> V.Vector Int -> Position -> PositionResult Position lookupPos end prevs nexts xs (Position line col)- | line < 0 = PositionRange (Position 0 0) (Position 0 0)- | line >= V.length xs = PositionRange (Position end 0) (Position end 0)- | otherwise = case V.unsafeIndex xs line of+ | line >= fromIntegral (V.length xs) = PositionRange (Position end 0) (Position end 0)+ | otherwise = case V.unsafeIndex xs (fromIntegral line) of -1 -> -- look for the previous and next lines that mapped successfully- let !prev = 1 + V.unsafeIndex prevs line- !next = V.unsafeIndex nexts line- in PositionRange (Position prev 0) (Position next 0)- line' -> PositionExact (Position line' col)+ let !prev = 1 + V.unsafeIndex prevs (fromIntegral line)+ !next = V.unsafeIndex nexts (fromIntegral line)+ in PositionRange (Position (fromIntegral prev) 0) (Position (fromIntegral next) 0)+ line' -> PositionExact (Position (fromIntegral line') col) -- Construct a mapping between lines in the diff -- -1 for unsucessful mapping
src/Development/IDE/Core/ProgressReporting.hs view
@@ -9,27 +9,31 @@ , mRunLspTCallback -- for tests , recordProgress- , InProgress(..)+ , InProgressState(..) ) where import Control.Concurrent.Async+import Control.Concurrent.STM.Stats (TVar, atomicallyNamed,+ modifyTVar', newTVarIO,+ readTVarIO) import Control.Concurrent.Strict import Control.Monad.Extra import Control.Monad.IO.Class import Control.Monad.Trans.Class (lift) import Data.Foldable (for_) import Data.Functor (($>))-import qualified Data.HashMap.Strict as HMap import qualified Data.Text as T import Data.Unique import Development.IDE.GHC.Orphans () import Development.IDE.Graph hiding (ShakeValue) import Development.IDE.Types.Location import Development.IDE.Types.Options+import qualified Focus import qualified Language.LSP.Server as LSP import Language.LSP.Types import qualified Language.LSP.Types as LSP+import qualified StmContainers.Map as STM import System.Time.Extra import UnliftIO.Exception (bracket_) @@ -69,26 +73,33 @@ updateState _ StopProgress st = pure st -- | Data structure to track progress across the project-data InProgress = InProgress- { todo :: !Int -- ^ Number of files to do- , done :: !Int -- ^ Number of files done- , current :: !(HMap.HashMap NormalizedFilePath Int)+data InProgressState = InProgressState+ { todoVar :: TVar Int -- ^ Number of files to do+ , doneVar :: TVar Int -- ^ Number of files done+ , currentVar :: STM.Map NormalizedFilePath Int } -recordProgress :: NormalizedFilePath -> (Int -> Int) -> InProgress -> InProgress-recordProgress file shift InProgress{..} = case HMap.alterF alter file current of- ((prev, new), m') ->- let (done',todo') =- case (prev,new) of- (Nothing,0) -> (done+1, todo+1)- (Nothing,_) -> (done, todo+1)- (Just 0, 0) -> (done , todo)- (Just 0, _) -> (done-1, todo)- (Just _, 0) -> (done+1, todo)- (Just _, _) -> (done , todo)- in InProgress todo' done' m'+newInProgress :: IO InProgressState+newInProgress = InProgressState <$> newTVarIO 0 <*> newTVarIO 0 <*> STM.newIO++recordProgress :: InProgressState -> NormalizedFilePath -> (Int -> Int) -> IO ()+recordProgress InProgressState{..} file shift = do+ (prev, new) <- atomicallyNamed "recordProgress" $ STM.focus alterPrevAndNew file currentVar+ atomicallyNamed "recordProgress2" $ do+ case (prev,new) of+ (Nothing,0) -> modifyTVar' doneVar (+1) >> modifyTVar' todoVar (+1)+ (Nothing,_) -> modifyTVar' todoVar (+1)+ (Just 0, 0) -> pure ()+ (Just 0, _) -> modifyTVar' doneVar pred+ (Just _, 0) -> modifyTVar' doneVar (+1)+ (Just _, _) -> pure() where- alter x = let x' = maybe (shift 0) shift x in ((x,x'), Just x')+ alterPrevAndNew = do+ prev <- Focus.lookup+ Focus.alter alter+ new <- Focus.lookupWithDefault 0+ return (prev, new)+ alter x = let x' = maybe (shift 0) shift x in Just x' -- | A 'ProgressReporting' that enqueues Begin and End notifications in a new -- thread, with a grace period (nothing will be sent if 'KickCompleted' arrives@@ -100,17 +111,16 @@ -> ProgressReportingStyle -> IO ProgressReporting delayedProgressReporting before after lspEnv optProgressStyle = do- inProgressVar <- newVar $ InProgress 0 0 mempty+ inProgressState <- newInProgress progressState <- newVar NotStarted let progressUpdate event = updateStateVar $ Event event progressStop = updateStateVar StopProgress- updateStateVar = modifyVar_ progressState . updateState (mRunLspT lspEnv $ lspShakeProgress inProgressVar)+ updateStateVar = modifyVar_ progressState . updateState (mRunLspT lspEnv $ lspShakeProgress inProgressState) - inProgress :: NormalizedFilePath -> Action a -> Action a- inProgress = withProgressVar inProgressVar+ inProgress = updateStateForFile inProgressState return ProgressReporting{..} where- lspShakeProgress inProgress = do+ lspShakeProgress InProgressState{..} = do -- first sleep a bit, so we only show progress messages if it's going to take -- a "noticable amount of time" (we often expect a thread kill to arrive before the sleep finishes) liftIO $ sleep before@@ -142,12 +152,17 @@ } loop _ _ | optProgressStyle == NoProgress = forever $ liftIO $ threadDelay maxBound- loop id prev = do- InProgress{..} <- liftIO $ readVar inProgress+ loop id prevPct = do+ done <- liftIO $ readTVarIO doneVar+ todo <- liftIO $ readTVarIO todoVar liftIO $ sleep after if todo == 0 then loop id 0 else do- let next = 100 * fromIntegral done / fromIntegral todo- when (next /= prev) $+ let+ nextFrac :: Double+ nextFrac = fromIntegral done / fromIntegral todo+ nextPct :: UInt+ nextPct = floor $ 100 * nextFrac+ when (nextPct /= prevPct) $ LSP.sendNotification LSP.SProgress $ LSP.ProgressParams { _token = id@@ -160,18 +175,18 @@ Percentage -> LSP.WorkDoneProgressReportParams { _cancellable = Nothing , _message = Nothing- , _percentage = Just next+ , _percentage = Just nextPct } NoProgress -> error "unreachable" }- loop id next+ loop id nextPct - withProgressVar var file = actionBracket (f succ) (const $ f pred) . const+ updateStateForFile inProgress file = actionBracket (f succ) (const $ f pred) . const -- This functions are deliberately eta-expanded to avoid space leaks. -- Do not remove the eta-expansion without profiling a session with at -- least 1000 modifications. where- f shift = modifyVar' var $ recordProgress file shift+ f shift = recordProgress inProgress file shift mRunLspT :: Applicative m => Maybe (LSP.LanguageContextEnv c ) -> LSP.LspT c m () -> m () mRunLspT (Just lspEnv) f = LSP.runLspT lspEnv f
src/Development/IDE/Core/RuleTypes.hs view
@@ -12,6 +12,7 @@ -- using the "Shaker" abstraction layer for in-memory use. -- module Development.IDE.Core.RuleTypes(+ GhcSessionDeps(.., GhcSessionDeps), module Development.IDE.Core.RuleTypes ) where @@ -42,7 +43,8 @@ import Development.IDE.Spans.LocalBindings import Development.IDE.Types.Diagnostics import GHC.Serialized (Serialized)-import Language.LSP.Types (NormalizedFilePath)+import Language.LSP.Types (Int32,+ NormalizedFilePath) data LinkableType = ObjectLinkable | BCOLinkable deriving (Eq,Ord,Show, Generic)@@ -289,13 +291,13 @@ type instance RuleResult GetModificationTime = FileVersion data FileVersion- = VFSVersion !Int+ = VFSVersion !Int32 | ModificationTime !POSIXTime deriving (Show, Generic) instance NFData FileVersion -vfsVersion :: FileVersion -> Maybe Int+vfsVersion :: FileVersion -> Maybe Int32 vfsVersion (VFSVersion i) = Just i vfsVersion ModificationTime{} = Nothing @@ -407,9 +409,15 @@ instance Hashable GhcSession instance NFData GhcSession -data GhcSessionDeps = GhcSessionDeps deriving (Eq, Show, Typeable, Generic)-instance Hashable GhcSessionDeps-instance NFData GhcSessionDeps+newtype GhcSessionDeps = GhcSessionDeps_+ { -- | Load full ModSummary values in the GHC session.+ -- Required for interactive evaluation, but leads to more cache invalidations+ fullModSummary :: Bool+ }+ deriving newtype (Eq, Show, Typeable, Hashable, NFData)++pattern GhcSessionDeps :: GhcSessionDeps+pattern GhcSessionDeps = GhcSessionDeps_ False data GetModIfaceFromDisk = GetModIfaceFromDisk deriving (Eq, Show, Typeable, Generic)
src/Development/IDE/Core/Rules.hs view
@@ -76,7 +76,6 @@ import qualified Data.Aeson.Types as A import qualified Data.Binary as B import qualified Data.ByteString as BS-import Data.ByteString.Encoding as T import qualified Data.ByteString.Lazy as LBS import Data.Coerce import Data.Foldable@@ -106,8 +105,7 @@ import Development.IDE.Core.RuleTypes import Development.IDE.Core.Service import Development.IDE.Core.Shake-import Development.IDE.GHC.Compat.Env-import Development.IDE.GHC.Compat.Core hiding+import Development.IDE.GHC.Compat hiding (parseModule, TargetId(..), loadInterface,@@ -131,14 +129,13 @@ import qualified Development.IDE.Types.Logger as L import Development.IDE.Types.Options import GHC.Generics (Generic)-import GHC.IO.Encoding import qualified GHC.LanguageExtensions as LangExt import qualified HieDb import Ide.Plugin.Config import qualified Language.LSP.Server as LSP-import Language.LSP.Types (SMethod (SCustomMethod))+import Language.LSP.Types (SMethod (SCustomMethod, SWindowShowMessage), ShowMessageParams (ShowMessageParams), MessageType (MtInfo)) import Language.LSP.VFS-import System.Directory (canonicalizePath, makeAbsolute)+import System.Directory (makeAbsolute) import Data.Default (def, Default) import Ide.Plugin.Properties (HasProperty, KeyNameProxy,@@ -148,7 +145,14 @@ import Ide.PluginUtils (configForPlugin) import Ide.Types (DynFlagsModifications (dynFlagsModifyGlobal, dynFlagsModifyParser), PluginId)+import Control.Concurrent.STM.Stats (atomically)+import Language.LSP.Server (LspT)+import System.Info.Extra (isWindows)+import HIE.Bios.Ghc.Gap (hostIsDynamic) +templateHaskellInstructions :: T.Text+templateHaskellInstructions = "https://haskell-language-server.readthedocs.io/en/latest/troubleshooting.html#static-binaries"+ -- | This is useful for rules to convert rules that can only produce errors or -- a result into the more general IdeResult type that supports producing -- warnings while also producing a result.@@ -513,21 +517,20 @@ getHieAstsRule = define $ \GetHieAst f -> do tmr <- use_ TypeCheck f- hsc <- hscEnv <$> use_ GhcSession f+ hsc <- hscEnv <$> use_ GhcSessionDeps f getHieAstRuleDefinition f hsc tmr persistentHieFileRule :: Rules () persistentHieFileRule = addPersistentRule GetHieAst $ \file -> runMaybeT $ do res <- readHieFileForSrcFromDisk file vfs <- asks vfs- encoding <- liftIO getLocaleEncoding (currentSource,ver) <- liftIO $ do mvf <- getVirtualFile vfs $ filePathToUri' file case mvf of- Nothing -> (,Nothing) . T.decode encoding <$> BS.readFile (fromNormalizedFilePath file)+ Nothing -> (,Nothing) . T.decodeUtf8 <$> BS.readFile (fromNormalizedFilePath file) Just vf -> pure (Rope.toText $ _text vf, Just $ _lsp_version vf) let refmap = Compat.generateReferencesMap . Compat.getAsts . Compat.hie_asts $ res- del = deltaFromDiff (T.decode encoding $ Compat.hie_hs_src res) currentSource+ del = deltaFromDiff (T.decodeUtf8 $ Compat.hie_hs_src res) currentSource pure (HAR (Compat.hie_module res) (Compat.hie_asts res) refmap mempty (HieFromDisk res),del,ver) getHieAstRuleDefinition :: NormalizedFilePath -> HscEnv -> TcModuleResult -> Action (IdeResult HieAstResult)@@ -589,9 +592,9 @@ readHieFileForSrcFromDisk :: NormalizedFilePath -> MaybeT IdeAction Compat.HieFile readHieFileForSrcFromDisk file = do- db <- asks hiedb+ ShakeExtras{withHieDb} <- ask log <- asks $ L.logDebug . logger- row <- MaybeT $ liftIO $ HieDb.lookupHieFileFromSource db $ fromNormalizedFilePath file+ row <- MaybeT $ liftIO $ withHieDb (\hieDb -> HieDb.lookupHieFileFromSource hieDb $ fromNormalizedFilePath file) let hie_loc = HieDb.hieModuleHieFile row liftIO $ log $ "LOADING HIE FILE :" <> T.pack (show file) exceptToMaybeT $ readHieFileFromDisk hie_loc@@ -696,22 +699,23 @@ Nothing -> LBS.toStrict $ B.encode (hash (snd val)) return (Just cutoffHash, val) - defineNoDiagnostics $ \GhcSessionDeps file -> do+ defineNoDiagnostics $ \(GhcSessionDeps_ fullModSummary) file -> do env <- use_ GhcSession file- ghcSessionDepsDefinition ghcSessionDepsConfig env file+ ghcSessionDepsDefinition fullModSummary ghcSessionDepsConfig env file -data GhcSessionDepsConfig = GhcSessionDepsConfig+newtype GhcSessionDepsConfig = GhcSessionDepsConfig { checkForImportCycles :: Bool- , fullModSummary :: Bool } instance Default GhcSessionDepsConfig where def = GhcSessionDepsConfig { checkForImportCycles = True- , fullModSummary = False } -ghcSessionDepsDefinition :: GhcSessionDepsConfig -> HscEnvEq -> NormalizedFilePath -> Action (Maybe HscEnvEq)-ghcSessionDepsDefinition GhcSessionDepsConfig{..} env file = do+ghcSessionDepsDefinition+ :: -- | full mod summary+ Bool ->+ GhcSessionDepsConfig -> HscEnvEq -> NormalizedFilePath -> Action (Maybe HscEnvEq)+ghcSessionDepsDefinition fullModSummary GhcSessionDepsConfig{..} env file = do let hsc = hscEnv env mbdeps <- mapM(fmap artifactFilePath . snd) <$> use_ GetLocatedImports file@@ -723,7 +727,7 @@ then uses_ GetModSummary deps else uses_ GetModSummaryWithoutTimestamps deps - depSessions <- map hscEnv <$> uses_ GhcSessionDeps deps+ depSessions <- map hscEnv <$> uses_ (GhcSessionDeps_ fullModSummary) deps ifaces <- uses_ GetModIface deps let inLoadOrder = map hirHomeMod ifaces@@ -762,14 +766,14 @@ -- doesn't need early cutoff since all its dependencies already have it defineNoDiagnostics $ \GetModIfaceFromDiskAndIndex f -> do x <- use_ GetModIfaceFromDisk f- se@ShakeExtras{hiedb} <- getShakeExtras+ se@ShakeExtras{withHieDb} <- getShakeExtras -- GetModIfaceFromDisk should have written a `.hie` file, must check if it matches version in db let ms = hirModSummary x hie_loc = Compat.ml_hie_file $ ms_location ms hash <- liftIO $ Util.getFileHash hie_loc- mrow <- liftIO $ HieDb.lookupHieFileFromSource hiedb (fromNormalizedFilePath f)- hie_loc' <- liftIO $ traverse (canonicalizePath . HieDb.hieModuleHieFile) mrow+ mrow <- liftIO $ withHieDb (\hieDb -> HieDb.lookupHieFileFromSource hieDb (fromNormalizedFilePath f))+ hie_loc' <- liftIO $ traverse (makeAbsolute . HieDb.hieModuleHieFile) mrow case mrow of Just row | hash == HieDb.modInfoHash (HieDb.hieModInfo row)@@ -818,8 +822,26 @@ summarize SourceUnmodified = BS.singleton 2 summarize SourceUnmodifiedAndStable = BS.singleton 3 +displayTHWarning :: LspT c IO ()+displayTHWarning+ | not isWindows && not hostIsDynamic = do+ LSP.sendNotification SWindowShowMessage $+ ShowMessageParams MtInfo $ T.unwords+ [ "This HLS binary does not support Template Haskell."+ , "Follow the [instructions](" <> templateHaskellInstructions <> ")"+ , "to build an HLS binary with support for Template Haskell."+ ]+ | otherwise = return ()++newtype DisplayTHWarning = DisplayTHWarning (IO ())+instance IsIdeGlobal DisplayTHWarning+ getModSummaryRule :: Rules () getModSummaryRule = do+ env <- lspEnv <$> getShakeExtrasRules+ displayItOnce <- liftIO $ once $ LSP.runLspT (fromJust env) displayTHWarning+ addIdeGlobal (DisplayTHWarning displayItOnce)+ defineEarlyCutoff $ Rule $ \GetModSummary f -> do session' <- hscEnv <$> use_ GhcSession f modify_dflags <- getModifyDynFlags dynFlagsModifyGlobal@@ -830,6 +852,10 @@ getModSummaryFromImports session fp modTime (textToStringBuffer <$> mFileContent) case modS of Right res -> do+ -- Check for Template Haskell+ when (uses_th_qq $ msrModSummary res) $ do+ DisplayTHWarning act <- getIdeGlobalAction+ liftIO act bufFingerPrint <- liftIO $ fingerprintFromStringBuffer $ fromJust $ ms_hspp_buf $ msrModSummary res let fingerPrint = Util.fingerprintFingerprints@@ -1025,9 +1051,6 @@ pure (Just $ encodeLinkableType res, Just res) where- uses_th_qq (ms_hspp_opts -> dflags) =- xopt LangExt.TemplateHaskell dflags || xopt LangExt.QuasiQuotes dflags- computeLinkableType :: ModSummary -> [Maybe ModSummary] -> [Maybe LinkableType] -> Maybe LinkableType computeLinkableType this deps xs | Just ObjectLinkable `elem` xs = Just ObjectLinkable -- If any dependent needs object code, so do we@@ -1037,6 +1060,10 @@ where this_type = computeLinkableTypeForDynFlags (ms_hspp_opts this) +uses_th_qq :: ModSummary -> Bool+uses_th_qq (ms_hspp_opts -> dflags) =+ xopt LangExt.TemplateHaskell dflags || xopt LangExt.QuasiQuotes dflags+ -- | How should we compile this module? -- (assuming we do in fact need to compile it). -- Depends on whether it uses unboxed tuples or sums@@ -1061,7 +1088,7 @@ extras <- getShakeExtras let targetPath = Compat.ml_hi_file $ ms_location $ hirModSummary hiFile liftIO $ do- resetInterfaceStore extras $ toNormalizedFilePath' targetPath+ atomically $ resetInterfaceStore extras $ toNormalizedFilePath' targetPath writeHiFile hsc hiFile data RulesConfig = RulesConfig
src/Development/IDE/Core/Service.hs view
@@ -30,7 +30,8 @@ import Control.Monad import Development.IDE.Core.Shake-import System.Environment (lookupEnv)+import Development.IDE.Types.Shake (WithHieDb)+import System.Environment (lookupEnv) ------------------------------------------------------------@@ -44,10 +45,10 @@ -> Debouncer LSP.NormalizedUri -> IdeOptions -> VFSHandle- -> HieDb+ -> WithHieDb -> IndexQueue -> IO IdeState-initialise defaultConfig mainRule lspEnv logger debouncer options vfs hiedb hiedbChan = do+initialise defaultConfig mainRule lspEnv logger debouncer options vfs withHieDb hiedbChan = do shakeProfiling <- do let fromConf = optShakeProfiling options fromEnv <- lookupEnv "GHCIDE_BUILD_PROFILING"@@ -60,7 +61,7 @@ shakeProfiling (optReportProgress options) (optTesting options)- hiedb+ withHieDb hiedbChan vfs (optShakeOptions options)
src/Development/IDE/Core/Shake.hs view
@@ -5,6 +5,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE PackageImports #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecursiveDo #-}@@ -147,24 +148,26 @@ import Language.LSP.Types.Capabilities import OpenTelemetry.Eventlog +import Control.Concurrent.STM.Stats (atomicallyNamed) import Control.Exception.Extra hiding (bracket_) import Data.Aeson (toJSON) import qualified Data.ByteString.Char8 as BS8 import Data.Coerce (coerce) import Data.Default-import Data.Foldable (toList)+import Data.Foldable (for_, toList) import Data.HashSet (HashSet) import qualified Data.HashSet as HSet-import Data.IORef.Extra (atomicModifyIORef'_,- atomicModifyIORef_) import Data.String (fromString) import Data.Text (pack) import Debug.Trace.Flags (userTracingEnabled) import qualified Development.IDE.Types.Exports as ExportsMap+import qualified Focus import HieDb.Types import Ide.Plugin.Config import qualified Ide.PluginUtils as HLS import Ide.Types (PluginId)+import qualified "list-t" ListT+import qualified StmContainers.Map as STM -- | We need to serialize writes to the database, so we send any function that -- needs to write to the database over the channel, where it will be picked up by@@ -179,7 +182,9 @@ } -- | Actions to queue up on the index worker thread-type IndexQueue = TQueue (HieDb -> IO ())+-- The inner `(HieDb -> IO ()) -> IO ()` wraps `HieDb -> IO ()`+-- with (currently) retry functionality+type IndexQueue = TQueue (((HieDb -> IO ()) -> IO ()) -> IO ()) -- information we stash inside the shakeExtra field data ShakeExtras = ShakeExtras@@ -187,14 +192,16 @@ lspEnv :: Maybe (LSP.LanguageContextEnv Config) ,debouncer :: Debouncer NormalizedUri ,logger :: Logger- ,globals :: Var (HMap.HashMap TypeRep Dynamic)- ,state :: Var Values- ,diagnostics :: Var DiagnosticStore- ,hiddenDiagnostics :: Var DiagnosticStore- ,publishedDiagnostics :: Var (HMap.HashMap NormalizedUri [Diagnostic])+ ,globals :: TVar (HMap.HashMap TypeRep Dynamic)+ -- ^ Registry of global state used by rules.+ -- Small and immutable after startup, so not worth using an STM.Map.+ ,state :: Values+ ,diagnostics :: STMDiagnosticStore+ ,hiddenDiagnostics :: STMDiagnosticStore+ ,publishedDiagnostics :: STM.Map NormalizedUri [Diagnostic] -- ^ This represents the set of diagnostics that we have published. -- Due to debouncing not every change might get published.- ,positionMapping :: Var (HMap.HashMap NormalizedUri (Map TextDocumentVersion (PositionDelta, PositionMapping)))+ ,positionMapping :: STM.Map NormalizedUri (Map TextDocumentVersion (PositionDelta, PositionMapping)) -- ^ Map from a text document version to a PositionMapping that describes how to map -- positions in a version of that document to positions in the latest version -- First mapping is delta from previous version and second one is an@@ -208,21 +215,22 @@ -> IO () ,ideNc :: IORef NameCache -- | A mapping of module name to known target (or candidate targets, if missing)- ,knownTargetsVar :: Var (Hashed KnownTargets)+ ,knownTargetsVar :: TVar (Hashed KnownTargets) -- | A mapping of exported identifiers for local modules. Updated on kick- ,exportsMap :: Var ExportsMap+ ,exportsMap :: TVar ExportsMap -- | A work queue for actions added via 'runInShakeSession' ,actionQueue :: ActionQueue ,clientCapabilities :: ClientCapabilities- , hiedb :: HieDb -- ^ Use only to read.+ , withHieDb :: WithHieDb -- ^ Use only to read. , hiedbWriter :: HieDbWriter -- ^ use to write- , persistentKeys :: Var (HMap.HashMap Key GetStalePersistent)+ , persistentKeys :: TVar (HMap.HashMap Key GetStalePersistent) -- ^ Registery for functions that compute/get "stale" results for the rule -- (possibly from disk)+ -- Small and immutable after startup, so not worth using an STM.Map. , vfs :: VFSHandle , defaultConfig :: Config -- ^ Default HLS config, only relevant if the client does not provide any Config- , dirtyKeys :: IORef (HashSet Key)+ , dirtyKeys :: TVar (HashSet Key) -- ^ Set of dirty rule keys since the last Shake run } @@ -256,7 +264,7 @@ addPersistentRule :: IdeRule k v => k -> (NormalizedFilePath -> IdeAction (Maybe (v,PositionDelta,TextDocumentVersion))) -> Rules () addPersistentRule k getVal = do ShakeExtras{persistentKeys} <- getShakeExtrasRules- void $ liftIO $ modifyVar' persistentKeys $ HMap.insert (Key k) (fmap (fmap (first3 toDyn)) . getVal)+ void $ liftIO $ atomically $ modifyTVar' persistentKeys $ HMap.insert (Key k) (fmap (fmap (first3 toDyn)) . getVal) class Typeable a => IsIdeGlobal a where @@ -280,15 +288,15 @@ addIdeGlobalExtras :: IsIdeGlobal a => ShakeExtras -> a -> IO () addIdeGlobalExtras ShakeExtras{globals} x@(typeOf -> ty) =- void $ liftIO $ modifyVarIO' globals $ \mp -> case HMap.lookup ty mp of- Just _ -> errorIO $ "Internal error, addIdeGlobalExtras, got the same type twice for " ++ show ty- Nothing -> return $! HMap.insert ty (toDyn x) mp+ void $ liftIO $ atomically $ modifyTVar' globals $ \mp -> case HMap.lookup ty mp of+ Just _ -> error $ "Internal error, addIdeGlobalExtras, got the same type twice for " ++ show ty+ Nothing -> HMap.insert ty (toDyn x) mp getIdeGlobalExtras :: forall a . IsIdeGlobal a => ShakeExtras -> IO a getIdeGlobalExtras ShakeExtras{globals} = do let typ = typeRep (Proxy :: Proxy a)- x <- HMap.lookup (typeRep (Proxy :: Proxy a)) <$> readVar globals+ x <- HMap.lookup (typeRep (Proxy :: Proxy a)) <$> readTVarIO globals case x of Just x | Just x <- fromDynamic x -> pure x@@ -308,7 +316,14 @@ getIdeOptions :: Action IdeOptions getIdeOptions = do GlobalIdeOptions x <- getIdeGlobalAction- return x+ env <- lspEnv <$> getShakeExtras+ case env of+ Nothing -> return x+ Just env -> do+ config <- liftIO $ LSP.runLspT env HLS.getClientConfig+ return x{optCheckProject = pure $ checkProject config,+ optCheckParents = pure $ checkParents config+ } getIdeOptionsIO :: ShakeExtras -> IO IdeOptions getIdeOptionsIO ide = do@@ -319,26 +334,24 @@ -- for the version of that value. lastValueIO :: IdeRule k v => ShakeExtras -> k -> NormalizedFilePath -> IO (Maybe (v, PositionMapping)) lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k file = do- hm <- readVar state- allMappings <- readVar positionMapping let readPersistent | IdeTesting testing <- ideTesting s -- Don't read stale persistent values in tests , testing = pure Nothing | otherwise = do- pmap <- readVar persistentKeys+ pmap <- readTVarIO persistentKeys mv <- runMaybeT $ do liftIO $ Logger.logDebug (logger s) $ T.pack $ "LOOKUP UP PERSISTENT FOR: " ++ show k f <- MaybeT $ pure $ HMap.lookup (Key k) pmap (dv,del,ver) <- MaybeT $ runIdeAction "lastValueIO" s $ f file MaybeT $ pure $ (,del,ver) <$> fromDynamic dv- case mv of+ atomicallyNamed "lastValueIO" $ case mv of Nothing -> do- void $ modifyVar' state $ HMap.alter (alterValue $ Failed True) (toKey k file)+ STM.focus (Focus.alter (alterValue $ Failed True)) (toKey k file) state return Nothing Just (v,del,ver) -> do- void $ modifyVar' state $ HMap.alter (alterValue $ Stale (Just del) ver (toDyn v)) (toKey k file)- return $ Just (v,addDelta del $ mappingForVersion allMappings file ver)+ STM.focus (Focus.alter (alterValue $ Stale (Just del) ver (toDyn v))) (toKey k file) state+ Just . (v,) . addDelta del <$> mappingForVersion positionMapping file ver -- We got a new stale value from the persistent rule, insert it in the map without affecting diagnostics alterValue new Nothing = Just (ValueWithDiagnostics new mempty) -- If it wasn't in the map, give it empty diagnostics@@ -348,11 +361,13 @@ -- Something already succeeded before, leave it alone _ -> old - case HMap.lookup (toKey k file) hm of+ atomicallyNamed "lastValueIO 4" (STM.lookup (toKey k file) state) >>= \case Nothing -> readPersistent Just (ValueWithDiagnostics v _) -> case v of- Succeeded ver (fromDynamic -> Just v) -> pure (Just (v, mappingForVersion allMappings file ver))- Stale del ver (fromDynamic -> Just v) -> pure (Just (v, maybe id addDelta del $ mappingForVersion allMappings file ver))+ Succeeded ver (fromDynamic -> Just v) ->+ atomicallyNamed "lastValueIO 5" $ Just . (v,) <$> mappingForVersion positionMapping file ver+ Stale del ver (fromDynamic -> Just v) ->+ atomicallyNamed "lastValueIO 6" $ Just . (v,) . maybe id addDelta del <$> mappingForVersion positionMapping file ver Failed p | not p -> readPersistent _ -> pure Nothing @@ -364,14 +379,13 @@ liftIO $ lastValueIO s key file mappingForVersion- :: HMap.HashMap NormalizedUri (Map TextDocumentVersion (a, PositionMapping))+ :: STM.Map NormalizedUri (Map TextDocumentVersion (a, PositionMapping)) -> NormalizedFilePath -> TextDocumentVersion- -> PositionMapping-mappingForVersion allMappings file ver =- maybe zeroMapping snd $- Map.lookup ver =<<- HMap.lookup (filePathToUri' file) allMappings+ -> STM PositionMapping+mappingForVersion allMappings file ver = do+ mapping <- STM.lookup (filePathToUri' file) allMappings+ return $ maybe zeroMapping snd $ Map.lookup ver =<< mapping type IdeRule k v = ( Shake.RuleResult k ~ v@@ -413,14 +427,14 @@ return (dir </> file) setValues :: IdeRule k v- => Var Values+ => Values -> k -> NormalizedFilePath -> Value v -> Vector FileDiagnostic- -> IO ()+ -> STM () setValues state key file val diags =- void $ modifyVar' state $ HMap.insert (toKey key file) (ValueWithDiagnostics (fmap toDyn val) diags)+ STM.insert (ValueWithDiagnostics (fmap toDyn val) diags) (toKey key file) state -- | Delete the value stored for a given ide build key@@ -429,55 +443,55 @@ => ShakeExtras -> k -> NormalizedFilePath- -> IO ()+ -> STM () deleteValue ShakeExtras{dirtyKeys, state} key file = do- void $ modifyVar' state $ HMap.delete (toKey key file)- atomicModifyIORef_ dirtyKeys $ HSet.insert (toKey key file)+ STM.delete (toKey key file) state+ modifyTVar' dirtyKeys $ HSet.insert (toKey key file) recordDirtyKeys :: Shake.ShakeValue k => ShakeExtras -> k -> [NormalizedFilePath]- -> IO ()-recordDirtyKeys ShakeExtras{dirtyKeys} key file = withEventTrace "recordDirtyKeys" $ \addEvent -> do- atomicModifyIORef_ dirtyKeys $ \x -> foldl' (flip HSet.insert) x (toKey key <$> file)- addEvent (fromString $ "dirty " <> show key) (fromString $ unlines $ map fromNormalizedFilePath file)-+ -> STM (IO ())+recordDirtyKeys ShakeExtras{dirtyKeys} key file = do+ modifyTVar' dirtyKeys $ \x -> foldl' (flip HSet.insert) x (toKey key <$> file)+ return $ withEventTrace "recordDirtyKeys" $ \addEvent -> do+ addEvent (fromString $ unlines $ "dirty " <> show key : map fromNormalizedFilePath file) -- | We return Nothing if the rule has not run and Just Failed if it has failed to produce a value. getValues :: forall k v. IdeRule k v =>- Var Values ->+ Values -> k -> NormalizedFilePath ->- IO (Maybe (Value v, Vector FileDiagnostic))+ STM (Maybe (Value v, Vector FileDiagnostic)) getValues state key file = do- vs <- readVar state- case HMap.lookup (toKey key file) vs of+ STM.lookup (toKey key file) state >>= \case Nothing -> pure Nothing Just (ValueWithDiagnostics v diagsV) -> do- let r = fmap (fromJust . fromDynamic @v) v+ let !r = seqValue $ fmap (fromJust . fromDynamic @v) v+ !res = (r,diagsV) -- Force to make sure we do not retain a reference to the HashMap -- and we blow up immediately if the fromJust should fail -- (which would be an internal error).- evaluate (r `seqValue` Just (r, diagsV))+ return $ Just res -- | Get all the files in the project knownTargets :: Action (Hashed KnownTargets) knownTargets = do ShakeExtras{knownTargetsVar} <- getShakeExtras- liftIO $ readVar knownTargetsVar+ liftIO $ readTVarIO knownTargetsVar -- | Seq the result stored in the Shake value. This only -- evaluates the value to WHNF not NF. We take care of the latter -- elsewhere and doing it twice is expensive.-seqValue :: Value v -> b -> b-seqValue v b = case v of- Succeeded ver v -> rnf ver `seq` v `seq` b- Stale d ver v -> rnf d `seq` rnf ver `seq` v `seq` b- Failed _ -> b+seqValue :: Value v -> Value v+seqValue val = case val of+ Succeeded ver v -> rnf ver `seq` v `seq` val+ Stale d ver v -> rnf d `seq` rnf ver `seq` v `seq` val+ Failed _ -> val -- | Open a 'IdeState', should be shut using 'shakeShut'. shakeOpen :: Maybe (LSP.LanguageContextEnv Config)@@ -487,37 +501,37 @@ -> Maybe FilePath -> IdeReportProgress -> IdeTesting- -> HieDb+ -> WithHieDb -> IndexQueue -> VFSHandle -> ShakeOptions -> Rules () -> IO IdeState shakeOpen lspEnv defaultConfig logger debouncer- shakeProfileDir (IdeReportProgress reportProgress) ideTesting@(IdeTesting testing) hiedb indexQueue vfs opts rules = mdo+ shakeProfileDir (IdeReportProgress reportProgress) ideTesting@(IdeTesting testing) withHieDb indexQueue vfs opts rules = mdo us <- mkSplitUniqSupply 'r' ideNc <- newIORef (initNameCache us knownKeyNames) shakeExtras <- do- globals <- newVar HMap.empty- state <- newVar HMap.empty- diagnostics <- newVar mempty- hiddenDiagnostics <- newVar mempty- publishedDiagnostics <- newVar mempty- positionMapping <- newVar HMap.empty- knownTargetsVar <- newVar $ hashed HMap.empty+ globals <- newTVarIO HMap.empty+ state <- STM.newIO+ diagnostics <- STM.newIO+ hiddenDiagnostics <- STM.newIO+ publishedDiagnostics <- STM.newIO+ positionMapping <- STM.newIO+ knownTargetsVar <- newTVarIO $ hashed HMap.empty let restartShakeSession = shakeRestart ideState- persistentKeys <- newVar HMap.empty+ persistentKeys <- newTVarIO HMap.empty indexPending <- newTVarIO HMap.empty indexCompleted <- newTVarIO 0 indexProgressToken <- newVar Nothing let hiedbWriter = HieDbWriter{..}- exportsMap <- newVar mempty+ exportsMap <- newTVarIO mempty -- lazily initialize the exports map with the contents of the hiedb _ <- async $ do logDebug logger "Initializing exports map from hiedb"- em <- createExportsMapHieDb hiedb- modifyVar' exportsMap (<> em)+ em <- createExportsMapHieDb withHieDb+ atomically $ modifyTVar' exportsMap (<> em) logDebug logger $ "Done initializing exports map from hiedb (" <> pack(show (ExportsMap.size em)) <> ")" progress <- do@@ -529,7 +543,7 @@ let clientCapabilities = maybe def LSP.resClientCapabilities lspEnv - dirtyKeys <- newIORef mempty+ dirtyKeys <- newTVarIO mempty pure ShakeExtras{..} (shakeDbM, shakeClose) <- shakeOpenDatabase@@ -559,8 +573,8 @@ IdeOptions{optCheckParents} <- getIdeOptionsIO extras checkParents <- optCheckParents regularly 1 $ do- readVar state >>= observe countKeys . countRelevantKeys checkParents . HMap.keys- readIORef dirtyKeys >>= observe countDirty . countRelevantKeys checkParents . HSet.toList+ observe countKeys . countRelevantKeys checkParents . map fst =<< (atomically . ListT.toList . STM.listT) state+ readTVarIO dirtyKeys >>= observe countDirty . countRelevantKeys checkParents . HSet.toList shakeGetBuildStep db >>= observe countBuilds | otherwise = async (pure ())@@ -571,15 +585,17 @@ -- | Must be called in the 'Initialized' handler and only once shakeSessionInit :: IdeState -> IO ()-shakeSessionInit IdeState{..} = do+shakeSessionInit ide@IdeState{..} = do initSession <- newSession shakeExtras shakeDb [] "shakeSessionInit" putMVar shakeSession initSession+ logDebug (ideLogger ide) "Shake session initialized" shakeShut :: IdeState -> IO ()-shakeShut IdeState{..} = withMVar shakeSession $ \runner -> do+shakeShut IdeState{..} = do+ runner <- tryReadMVar shakeSession -- Shake gets unhappy if you try to close when there is a running -- request so we first abort that.- void $ cancelShakeSession runner+ for_ runner cancelShakeSession void $ shakeDatabaseProfile shakeDb shakeClose progressStop $ progress shakeExtras@@ -617,8 +633,8 @@ (\runner -> do (stopTime,()) <- duration (cancelShakeSession runner) res <- shakeDatabaseProfile shakeDb- backlog <- readIORef $ dirtyKeys shakeExtras- queue <- atomically $ peekInProgress $ actionQueue shakeExtras+ backlog <- readTVarIO $ dirtyKeys shakeExtras+ queue <- atomicallyNamed "actionQueue - peek" $ peekInProgress $ actionQueue shakeExtras let profile = case res of Just fp -> ", profile saved at " <> fp _ -> ""@@ -651,7 +667,7 @@ shakeEnqueue :: ShakeExtras -> DelayedAction a -> IO (IO a) shakeEnqueue ShakeExtras{actionQueue, logger} act = do (b, dai) <- instantiateDelayedAction act- atomically $ pushQueue dai actionQueue+ atomicallyNamed "actionQueue - push" $ pushQueue dai actionQueue let wait' b = waitBarrier b `catches` [ Handler(\BlockedIndefinitelyOnMVar ->@@ -660,7 +676,7 @@ , Handler (\e@AsyncCancelled -> do logPriority logger Debug $ T.pack $ actionName act <> " was cancelled" - atomically $ abortQueue dai actionQueue+ atomicallyNamed "actionQueue - abort" $ abortQueue dai actionQueue throw e) ] return (wait' b >>= either throwIO return)@@ -675,29 +691,28 @@ -> IO ShakeSession newSession extras@ShakeExtras{..} shakeDb acts reason = do IdeOptions{optRunSubset} <- getIdeOptionsIO extras- reenqueued <- atomically $ peekInProgress actionQueue+ reenqueued <- atomicallyNamed "actionQueue - peek" $ peekInProgress actionQueue allPendingKeys <- if optRunSubset- then Just <$> readIORef dirtyKeys+ then Just <$> readTVarIO dirtyKeys else return Nothing let -- A daemon-like action used to inject additional work -- Runs actions from the work queue sequentially pumpActionThread otSpan = do- d <- liftIO $ atomically $ popQueue actionQueue+ d <- liftIO $ atomicallyNamed "action queue - pop" $ popQueue actionQueue actionFork (run otSpan d) $ \_ -> pumpActionThread otSpan -- TODO figure out how to thread the otSpan into defineEarlyCutoff run _otSpan d = do start <- liftIO offsetTime getAction d- liftIO $ atomically $ doneQueue d actionQueue+ liftIO $ atomicallyNamed "actionQueue - done" $ doneQueue d actionQueue runTime <- liftIO start let msg = T.pack $ "finish: " ++ actionName d ++ " (took " ++ showDuration runTime ++ ")" liftIO $ do logPriority logger (actionPriority d) msg- notifyTestingLogMessage extras msg -- The inferred type signature doesn't work in ghc >= 9.0.1 workRun :: (forall b. IO b -> IO b) -> IO (IO ())@@ -748,15 +763,13 @@ d' = DelayedAction (Just u) s p a' return (b, d') -getDiagnostics :: IdeState -> IO [FileDiagnostic]+getDiagnostics :: IdeState -> STM [FileDiagnostic] getDiagnostics IdeState{shakeExtras = ShakeExtras{diagnostics}} = do- val <- readVar diagnostics- return $ getAllDiagnostics val+ getAllDiagnostics diagnostics -getHiddenDiagnostics :: IdeState -> IO [FileDiagnostic]+getHiddenDiagnostics :: IdeState -> STM [FileDiagnostic] getHiddenDiagnostics IdeState{shakeExtras = ShakeExtras{hiddenDiagnostics}} = do- val <- readVar hiddenDiagnostics- return $ getAllDiagnostics val+ getAllDiagnostics hiddenDiagnostics -- | Find and release old keys from the state Hashmap -- For the record, there are other state sources that this process does not release:@@ -778,29 +791,30 @@ garbageCollectKeys :: String -> Int -> CheckParents -> [(Key, Int)] -> Action [Key] garbageCollectKeys label maxAge checkParents agedKeys = do start <- liftIO offsetTime- extras <- getShakeExtras- (n::Int, garbage) <- liftIO $ modifyVar (state extras) $ \vmap ->- evaluate $ foldl' removeDirtyKey (vmap, (0,[])) agedKeys- liftIO $ atomicModifyIORef_ (dirtyKeys extras) $ \x ->- foldl' (flip HSet.insert) x garbage+ ShakeExtras{state, dirtyKeys, lspEnv, logger, ideTesting} <- getShakeExtras+ (n::Int, garbage) <- liftIO $+ foldM (removeDirtyKey dirtyKeys state) (0,[]) agedKeys t <- liftIO start when (n>0) $ liftIO $ do- logDebug (logger extras) $ T.pack $+ logDebug logger $ T.pack $ label <> " of " <> show n <> " keys (took " <> showDuration t <> ")"- when (coerce $ ideTesting extras) $ liftIO $ mRunLspT (lspEnv extras) $+ when (coerce ideTesting) $ liftIO $ mRunLspT lspEnv $ LSP.sendNotification (SCustomMethod "ghcide/GC") (toJSON $ mapMaybe (fmap showKey . fromKeyType) garbage) return garbage where showKey = show . Q- removeDirtyKey st@(vmap,(!counter, keys)) (k, age)+ removeDirtyKey dk values st@(!counter, keys) (k, age) | age > maxAge , Just (kt,_) <- fromKeyType k , not(kt `HSet.member` preservedKeys checkParents)- , (True, vmap') <- HMap.alterF (\prev -> (isJust prev, Nothing)) k vmap- = (vmap', (counter+1, k:keys))- | otherwise = st+ = atomicallyNamed "GC" $ do+ gotIt <- STM.focus (Focus.member <* Focus.delete) k values+ when gotIt $+ modifyTVar' dk (HSet.insert k)+ return $ if gotIt then (counter+1, k:keys) else st+ | otherwise = pure st countRelevantKeys :: CheckParents -> [Key] -> Int countRelevantKeys checkParents =@@ -899,7 +913,7 @@ wait <- delayedAction $ mkDelayedAction ("C:" ++ show key ++ ":" ++ fromNormalizedFilePath file) Debug $ use key file s@ShakeExtras{state} <- askShake- r <- liftIO $ getValues state key file+ r <- liftIO $ atomicallyNamed "useStateFast" $ getValues state key file liftIO $ case r of -- block for the result if we haven't computed before Nothing -> do@@ -1008,7 +1022,7 @@ (if optSkipProgress options key then id else inProgress progress file) $ do val <- case old of Just old | mode == RunDependenciesSame -> do- v <- liftIO $ getValues state key file+ v <- liftIO $ atomicallyNamed "define - read 1" $ getValues state key file case v of -- No changes in the dependencies and we have -- an existing successful result.@@ -1027,10 +1041,10 @@ (do v <- action; liftIO $ evaluate $ force v) $ \(e :: SomeException) -> do pure (Nothing, ([ideErrorText file $ T.pack $ show e | not $ isBadDependency e],Nothing))- modTime <- liftIO $ (currentValue . fst =<<) <$> getValues state GetModificationTime file+ modTime <- liftIO $ (currentValue . fst =<<) <$> atomicallyNamed "define - read 2" (getValues state GetModificationTime file) (bs, res) <- case res of Nothing -> do- staleV <- liftIO $ getValues state key file+ staleV <- liftIO $ atomicallyNamed "define -read 3" $ getValues state key file pure $ case staleV of Nothing -> (toShakeValue ShakeResult bs, Failed False) Just v -> case v of@@ -1041,7 +1055,7 @@ (Failed b, _) -> (toShakeValue ShakeResult bs, Failed b) Just v -> pure (maybe ShakeNoCutoff ShakeResult bs, Succeeded (vfsVersion =<< modTime) v)- liftIO $ setValues state key file res (Vector.fromList diags)+ liftIO $ atomicallyNamed "define - write" $ setValues state key file res (Vector.fromList diags) doDiagnostics diags let eq = case (bs, fmap decodeShakeValue old) of (ShakeResult a, Just (ShakeResult b)) -> cmp a b@@ -1053,7 +1067,7 @@ (if eq then ChangedRecomputeSame else ChangedRecomputeDiff) (encodeShakeValue bs) $ A res- liftIO $ atomicModifyIORef'_ dirtyKeys (HSet.delete $ toKey key file)+ liftIO $ atomicallyNamed "define - dirtyKeys" $ modifyTVar' dirtyKeys (HSet.delete $ toKey key file) return res traceA :: A v -> String@@ -1141,34 +1155,30 @@ -> [(ShowDiagnostic,Diagnostic)] -- ^ current results -> m () updateFileDiagnostics fp k ShakeExtras{logger, diagnostics, hiddenDiagnostics, publishedDiagnostics, state, debouncer, lspEnv} current = liftIO $ do- modTime <- (currentValue . fst =<<) <$> getValues state GetModificationTime fp+ modTime <- (currentValue . fst =<<) <$> atomicallyNamed "diagnostics - read" (getValues state GetModificationTime fp) let (currentShown, currentHidden) = partition ((== ShowDiag) . fst) current uri = filePathToUri' fp ver = vfsVersion =<< modTime- update new store =- let store' = setStageDiagnostics uri ver (T.pack $ show k) new store- new' = getUriDiagnostics uri store'- in (store', new')+ update new store = setStageDiagnostics uri ver (T.pack $ show k) new store mask_ $ do -- Mask async exceptions to ensure that updated diagnostics are always -- published. Otherwise, we might never publish certain diagnostics if -- an exception strikes between modifyVar but before -- publishDiagnosticsNotification.- newDiags <- modifyVar diagnostics $ pure . update (map snd currentShown)- _ <- modifyVar hiddenDiagnostics $ pure . update (map snd currentHidden)+ newDiags <- liftIO $ atomicallyNamed "diagnostics - update" $ update (map snd currentShown) diagnostics+ _ <- liftIO $ atomicallyNamed "diagnostics - hidden" $ update (map snd currentHidden) hiddenDiagnostics let uri = filePathToUri' fp let delay = if null newDiags then 0.1 else 0 registerEvent debouncer delay uri $ do- join $ mask_ $ modifyVar publishedDiagnostics $ \published -> do- let lastPublish = HMap.lookupDefault [] uri published- !published' = HMap.insert uri newDiags published- action = when (lastPublish /= newDiags) $ case lspEnv of+ join $ mask_ $ do+ lastPublish <- atomicallyNamed "diagnostics - publish" $ STM.focus (Focus.lookupWithDefault [] <* Focus.insert newDiags) uri publishedDiagnostics+ let action = when (lastPublish /= newDiags) $ case lspEnv of Nothing -> -- Print an LSP event. logInfo logger $ showDiagnosticsColored $ map (fp,ShowDiag,) newDiags Just env -> LSP.runLspT env $ LSP.sendNotification LSP.STextDocumentPublishDiagnostics $- LSP.PublishDiagnosticsParams (fromNormalizedUri uri) ver (List newDiags)- return (published', action)+ LSP.PublishDiagnosticsParams (fromNormalizedUri uri) (fmap fromIntegral ver) (List newDiags)+ return action newtype Priority = Priority Double @@ -1183,10 +1193,21 @@ ShakeExtras{logger} <- getShakeExtras return logger +--------------------------------------------------------------------------------+type STMDiagnosticStore = STM.Map NormalizedUri StoreItem getDiagnosticsFromStore :: StoreItem -> [Diagnostic] getDiagnosticsFromStore (StoreItem _ diags) = concatMap SL.fromSortedList $ Map.elems diags +updateSTMDiagnostics :: STMDiagnosticStore+ -> NormalizedUri -> TextDocumentVersion -> DiagnosticsBySource+ -> STM [LSP.Diagnostic]+updateSTMDiagnostics store uri mv newDiagsBySource =+ getDiagnosticsFromStore . fromJust <$> STM.focus (Focus.alter update *> Focus.lookup) uri store+ where+ update (Just(StoreItem mvs dbs))+ | mvs == mv = Just (StoreItem mv (newDiagsBySource <> dbs))+ update _ = Just (StoreItem mv newDiagsBySource) -- | Sets the diagnostics for a file and compilation step -- if you want to clear the diagnostics call this with an empty list@@ -1195,38 +1216,29 @@ -> TextDocumentVersion -- ^ the time that the file these diagnostics originate from was last edited -> T.Text -> [LSP.Diagnostic]- -> DiagnosticStore- -> DiagnosticStore-setStageDiagnostics uri ver stage diags ds = updateDiagnostics ds uri ver updatedDiags+ -> STMDiagnosticStore+ -> STM [LSP.Diagnostic]+setStageDiagnostics uri ver stage diags ds = updateSTMDiagnostics ds uri ver updatedDiags where updatedDiags = Map.singleton (Just stage) (SL.toSortedList diags) getAllDiagnostics ::- DiagnosticStore ->- [FileDiagnostic]+ STMDiagnosticStore ->+ STM [FileDiagnostic] getAllDiagnostics =- concatMap (\(k,v) -> map (fromUri k,ShowDiag,) $ getDiagnosticsFromStore v) . HMap.toList--getUriDiagnostics ::- NormalizedUri ->- DiagnosticStore ->- [LSP.Diagnostic]-getUriDiagnostics uri ds =- maybe [] getDiagnosticsFromStore $- HMap.lookup uri ds+ fmap (concatMap (\(k,v) -> map (fromUri k,ShowDiag,) $ getDiagnosticsFromStore v)) . ListT.toList . STM.listT -updatePositionMapping :: IdeState -> VersionedTextDocumentIdentifier -> List TextDocumentContentChangeEvent -> IO ()-updatePositionMapping IdeState{shakeExtras = ShakeExtras{positionMapping}} VersionedTextDocumentIdentifier{..} (List changes) = do- modifyVar_ positionMapping $ \allMappings -> do- let uri = toNormalizedUri _uri- let mappingForUri = HMap.lookupDefault Map.empty uri allMappings- let (_, updatedMapping) =+updatePositionMapping :: IdeState -> VersionedTextDocumentIdentifier -> List TextDocumentContentChangeEvent -> STM ()+updatePositionMapping IdeState{shakeExtras = ShakeExtras{positionMapping}} VersionedTextDocumentIdentifier{..} (List changes) =+ STM.focus (Focus.alter f) uri positionMapping+ where+ uri = toNormalizedUri _uri+ f = Just . f' . fromMaybe mempty+ f' mappingForUri = snd $ -- Very important to use mapAccum here so that the tails of -- each mapping can be shared, otherwise quadratic space is -- used which is evident in long running sessions. Map.mapAccumRWithKey (\acc _k (delta, _) -> let new = addDelta delta acc in (new, (delta, acc))) zeroMapping (Map.insert _version (shared_change, zeroMapping) mappingForUri)- pure $ HMap.insert uri updatedMapping allMappings- where- shared_change = mkDelta changes+ shared_change = mkDelta changes
src/Development/IDE/Core/Tracing.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE NoApplicativeDo #-}+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE PatternSynonyms #-} {-# HLINT ignore #-} module Development.IDE.Core.Tracing ( otTracedHandler@@ -16,62 +17,65 @@ ) where -import Control.Concurrent.Async (Async, async)-import Control.Concurrent.Extra (Var, modifyVar_, newVar,- readVar, threadDelay)-import Control.Exception (evaluate)-import Control.Exception.Safe (SomeException, catch,- generalBracket)-import Control.Monad (forM_, forever, void, when,- (>=>))-import Control.Monad.Catch (ExitCase (..), MonadMask)-import Control.Monad.Extra (whenJust)+import Control.Concurrent.Async (Async, async)+import Control.Concurrent.Extra (modifyVar_, newVar, readVar,+ threadDelay)+import Control.Exception (evaluate)+import Control.Exception.Safe (SomeException, catch,+ generalBracket)+import Control.Monad (forM_, forever, void, when,+ (>=>))+import Control.Monad.Catch (ExitCase (..), MonadMask)+import Control.Monad.Extra (whenJust) import Control.Monad.IO.Unlift-import Control.Seq (r0, seqList, seqTuple2, using)-import Data.ByteString (ByteString)-import Data.ByteString.Char8 (pack)-import Data.Dynamic (Dynamic)-import qualified Data.HashMap.Strict as HMap-import Data.IORef (modifyIORef', newIORef,- readIORef, writeIORef)-import Data.String (IsString (fromString))-import qualified Data.Text as T-import Data.Text.Encoding (encodeUtf8)-import Data.Typeable (TypeRep, typeOf)-import Data.Word (Word16)-import Debug.Trace.Flags (userTracingEnabled)-import Development.IDE.Core.RuleTypes (GhcSession (GhcSession),- GhcSessionDeps (GhcSessionDeps),- GhcSessionIO (GhcSessionIO))-import Development.IDE.Graph (Action)+import Control.Monad.STM (atomically)+import Control.Seq (r0, seqList, seqTuple2,+ using)+import Data.ByteString (ByteString)+import Data.ByteString.Char8 (pack)+import qualified Data.HashMap.Strict as HMap+import Data.IORef (modifyIORef', newIORef,+ readIORef, writeIORef)+import Data.String (IsString (fromString))+import qualified Data.Text as T+import Data.Text.Encoding (encodeUtf8)+import Data.Typeable (TypeRep, typeOf)+import Data.Word (Word16)+import Debug.Trace.Flags (userTracingEnabled)+import Development.IDE.Core.RuleTypes (GhcSession (GhcSession),+ GhcSessionDeps (GhcSessionDeps),+ GhcSessionIO (GhcSessionIO))+import Development.IDE.Graph (Action) import Development.IDE.Graph.Rule-import Development.IDE.Types.Diagnostics (FileDiagnostic, showDiagnostics)-import Development.IDE.Types.Location (Uri (..))-import Development.IDE.Types.Logger (Logger (Logger), logDebug,- logInfo)-import Development.IDE.Types.Shake (Value,- ValueWithDiagnostics (..),- Values, fromKeyType)-import Foreign.Storable (Storable (sizeOf))-import HeapSize (recursiveSize, runHeapsize)-import Ide.PluginUtils (installSigUsr1Handler)-import Ide.Types (PluginId (..))-import Language.LSP.Types (NormalizedFilePath,- fromNormalizedFilePath)-import Numeric.Natural (Natural)-import OpenTelemetry.Eventlog (SpanInFlight (..), addEvent,- beginSpan, endSpan,- mkValueObserver, observe,- setTag, withSpan, withSpan_)+import Development.IDE.Types.Diagnostics (FileDiagnostic,+ showDiagnostics)+import Development.IDE.Types.Location (Uri (..))+import Development.IDE.Types.Logger (Logger (Logger), logDebug,+ logInfo)+import Development.IDE.Types.Shake (ValueWithDiagnostics (..),+ Values, fromKeyType)+import Foreign.Storable (Storable (sizeOf))+import HeapSize (recursiveSize, runHeapsize)+import Ide.PluginUtils (installSigUsr1Handler)+import Ide.Types (PluginId (..))+import Language.LSP.Types (NormalizedFilePath,+ fromNormalizedFilePath)+import qualified "list-t" ListT+import Numeric.Natural (Natural)+import OpenTelemetry.Eventlog (SpanInFlight (..), addEvent,+ beginSpan, endSpan,+ mkValueObserver, observe,+ setTag, withSpan, withSpan_)+import qualified StmContainers.Map as STM #if MIN_VERSION_ghc(8,8,0) otTracedProvider :: MonadUnliftIO m => PluginId -> ByteString -> m a -> m a otTracedGarbageCollection :: (MonadMask f, MonadIO f, Show a) => ByteString -> f [a] -> f [a]-withEventTrace :: (MonadMask m, MonadIO m) => String -> ((ByteString -> ByteString -> m ()) -> m a) -> m a+withEventTrace :: (MonadMask m, MonadIO m) => String -> ((ByteString -> m ()) -> m a) -> m a #else otTracedProvider :: MonadUnliftIO m => PluginId -> String -> m a -> m a otTracedGarbageCollection :: (MonadMask f, MonadIO f, Show a) => String -> f [a] -> f [a]-withEventTrace :: (MonadMask m, MonadIO m) => String -> ((String -> ByteString -> m ()) -> m a) -> m a+withEventTrace :: (MonadMask m, MonadIO m) => String -> ((ByteString -> m ()) -> m a) -> m a #endif withTrace :: (MonadMask m, MonadIO m) =>@@ -86,8 +90,8 @@ withEventTrace name act | userTracingEnabled = withSpan (fromString name) $ \sp -> do- act (addEvent sp)- | otherwise = act (\_ _ -> pure ())+ act (addEvent sp "")+ | otherwise = act (\_ -> pure ()) -- | Returns a logger that produces telemetry events in a single span withTelemetryLogger :: (MonadIO m, MonadMask m) => (Logger -> m a) -> m a@@ -174,16 +178,16 @@ | otherwise = act -startProfilingTelemetry :: Bool -> Logger -> Var Values -> IO ()-startProfilingTelemetry allTheTime logger stateRef = do+startProfilingTelemetry :: Bool -> Logger -> Values -> IO ()+startProfilingTelemetry allTheTime logger state = do instrumentFor <- getInstrumentCached installSigUsr1Handler $ do logInfo logger "SIGUSR1 received: performing memory measurement"- performMeasurement logger stateRef instrumentFor+ performMeasurement logger state instrumentFor when allTheTime $ void $ regularly (1 * seconds) $- performMeasurement logger stateRef instrumentFor+ performMeasurement logger state instrumentFor where seconds = 1000000 @@ -193,17 +197,16 @@ performMeasurement :: Logger ->- Var Values ->+ Values -> (Maybe String -> IO OurValueObserver) -> IO ()-performMeasurement logger stateRef instrumentFor = do-- values <- readVar stateRef+performMeasurement logger values instrumentFor = do+ contents <- atomically $ ListT.toList $ STM.listT values let keys = typeOf GhcSession : typeOf GhcSessionDeps -- TODO restore : [ kty- | k <- HMap.keys values+ | (k,_) <- contents , Just (kty,_) <- [fromKeyType k] -- do GhcSessionIO last since it closes over stateRef itself , kty /= typeOf GhcSession@@ -212,7 +215,7 @@ ] ++ [typeOf GhcSessionIO] groupedForSharing <- evaluate (keys `using` seqList r0)- measureMemory logger [groupedForSharing] instrumentFor stateRef+ measureMemory logger [groupedForSharing] instrumentFor values `catch` \(e::SomeException) -> logInfo logger ("MEMORY PROFILING ERROR: " <> fromString (show e)) @@ -243,12 +246,12 @@ :: Logger -> [[TypeRep]] -- ^ Grouping of keys for the sharing-aware analysis -> (Maybe String -> IO OurValueObserver)- -> Var Values+ -> Values -> IO ()-measureMemory logger groups instrumentFor stateRef = withSpan_ "Measure Memory" $ do- values <- readVar stateRef+measureMemory logger groups instrumentFor values = withSpan_ "Measure Memory" $ do+ contents <- atomically $ ListT.toList $ STM.listT values valuesSizeRef <- newIORef $ Just 0- let !groupsOfGroupedValues = groupValues values+ let !groupsOfGroupedValues = groupValues contents logDebug logger "STARTING MEMORY PROFILING" forM_ groupsOfGroupedValues $ \groupedValues -> do keepGoing <- readIORef valuesSizeRef@@ -277,12 +280,12 @@ logInfo logger "Memory profiling could not be completed: increase the size of your nursery (+RTS -Ax) and try again" where- groupValues :: Values -> [ [(String, [Value Dynamic])] ]- groupValues values =+ -- groupValues :: Values -> [ [(String, [Value Dynamic])] ]+ groupValues contents = let !groupedValues = [ [ (show ty, vv) | ty <- groupKeys- , let vv = [ v | (fromKeyType -> Just (kty,_), ValueWithDiagnostics v _) <- HMap.toList values+ , let vv = [ v | (fromKeyType -> Just (kty,_), ValueWithDiagnostics v _) <- contents , kty == ty] ] | groupKeys <- groups
src/Development/IDE/Core/UseStale.hs view
@@ -1,163 +1,162 @@-{-# LANGUAGE DerivingVia #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE RankNTypes #-}--module Development.IDE.Core.UseStale- ( Age(..)- , Tracked- , unTrack- , PositionMap- , TrackedStale (..)- , untrackedStaleValue- , unsafeMkStale- , unsafeMkCurrent- , unsafeCopyAge- , MapAge (..)- , dualPositionMap- , useWithStale- , useWithStale_- ) where--import Control.Arrow-import Control.Category (Category)-import qualified Control.Category as C-import Control.DeepSeq (NFData)-import Data.Aeson-import Data.Coerce (coerce)-import Data.Functor ((<&>))-import Data.Functor.Identity (Identity (Identity))-import Data.Kind (Type)-import Data.String (fromString)-import Development.IDE.GHC.Compat (RealSrcSpan,- srcSpanFile)-import Development.IDE.GHC.Compat.Util (unpackFS)-import Development.IDE (Action, IdeRule,- NormalizedFilePath,- Range,- rangeToRealSrcSpan,- realSrcSpanToRange)-import qualified Development.IDE.Core.PositionMapping as P-import qualified Development.IDE.Core.Shake as IDE------------------------------------------------------------------------------------ | A data kind for 'Tracked'.-data Age = Current | Stale Type------------------------------------------------------------------------------------ | Some value, tagged with its age. All 'Current' ages are considered to be--- the same thing, but 'Stale' values are protected by an untouchable variable--- to ensure they can't be unified.-newtype Tracked (age :: Age) a = UnsafeTracked- { unTrack :: a- }- deriving stock (Functor, Foldable, Traversable)- deriving newtype (Eq, Ord, Show, Read, ToJSON, FromJSON, NFData)- deriving (Applicative, Monad) via Identity------------------------------------------------------------------------------------ | Like 'P.PositionMapping', but with annotated ages for how 'Tracked' values--- change. Use the 'Category' instance to compose 'PositionMapping's in order--- to transform between values of different stale ages.-newtype PositionMap (from :: Age) (to :: Age) = PositionMap- { _getPositionMapping :: P.PositionMapping- }--instance Category PositionMap where- id = coerce P.zeroMapping- (.) = coerce P.composeDelta------------------------------------------------------------------------------------ | Get a 'PositionMap' that runs in the opposite direction.-dualPositionMap :: PositionMap from to -> PositionMap to from-dualPositionMap (PositionMap (P.PositionMapping (P.PositionDelta from to))) =- PositionMap $ P.PositionMapping $ P.PositionDelta to from------------------------------------------------------------------------------------ | A pair containing a @'Tracked' 'Stale'@ value, as well as--- a 'PositionMapping' that will fast-forward values to the current age.-data TrackedStale a where- TrackedStale- :: Tracked (Stale s) a- -> PositionMap (Stale s) Current- -> TrackedStale a--instance Functor TrackedStale where- fmap f (TrackedStale t pm) = TrackedStale (fmap f t) pm---untrackedStaleValue :: TrackedStale a -> a-untrackedStaleValue (TrackedStale ta _) = coerce ta------------------------------------------------------------------------------------ | A class for which 'Tracked' values can be run across a 'PositionMapping'--- to change their ages.-class MapAge a where- {-# MINIMAL mapAgeFrom | mapAgeTo #-}- mapAgeFrom :: PositionMap from to -> Tracked to a -> Maybe (Tracked from a)- mapAgeFrom = mapAgeTo . dualPositionMap-- mapAgeTo :: PositionMap from to -> Tracked from a -> Maybe (Tracked to a)- mapAgeTo = mapAgeFrom . dualPositionMap---instance MapAge Range where- mapAgeFrom = coerce P.fromCurrentRange- mapAgeTo = coerce P.toCurrentRange---instance MapAge RealSrcSpan where- mapAgeFrom =- invMapAge (\fs -> rangeToRealSrcSpan (fromString $ unpackFS fs))- (srcSpanFile &&& realSrcSpanToRange)- . mapAgeFrom------------------------------------------------------------------------------------ | Helper function for deriving 'MapAge' for values in terms of other--- instances.-invMapAge- :: (c -> a -> b)- -> (b -> (c, a))- -> (Tracked from a -> Maybe (Tracked to a))- -> Tracked from b- -> Maybe (Tracked to b)-invMapAge to from f t =- let (c, t') = unTrack $ fmap from t- in fmap (fmap $ to c) $ f $ UnsafeTracked t'---unsafeMkCurrent :: age -> Tracked 'Current age-unsafeMkCurrent = coerce---unsafeMkStale :: age -> Tracked (Stale s) age-unsafeMkStale = coerce---unsafeCopyAge :: Tracked age a -> b -> Tracked age b-unsafeCopyAge _ = coerce----- | Request a Rule result, it not available return the last computed result, if any, which may be stale-useWithStale :: IdeRule k v- => k -> NormalizedFilePath -> Action (Maybe (TrackedStale v))-useWithStale key file = do- x <- IDE.useWithStale key file- pure $ x <&> \(v, pm) ->- TrackedStale (coerce v) (coerce pm)---- | Request a Rule result, it not available return the last computed result which may be stale.--- Errors out if none available.-useWithStale_ :: IdeRule k v- => k -> NormalizedFilePath -> Action (TrackedStale v)-useWithStale_ key file = do- (v, pm) <- IDE.useWithStale_ key file- pure $ TrackedStale (coerce v) (coerce pm)-+{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE RankNTypes #-} + +module Development.IDE.Core.UseStale + ( Age(..) + , Tracked + , unTrack + , PositionMap + , TrackedStale (..) + , untrackedStaleValue + , unsafeMkStale + , unsafeMkCurrent + , unsafeCopyAge + , MapAge (..) + , dualPositionMap + , useWithStale + , useWithStale_ + ) where + +import Control.Arrow +import Control.Category (Category) +import qualified Control.Category as C +import Control.DeepSeq (NFData) +import Data.Aeson +import Data.Coerce (coerce) +import Data.Functor ((<&>)) +import Data.Functor.Identity (Identity (Identity)) +import Data.Kind (Type) +import Data.String (fromString) +import Development.IDE (Action, IdeRule, + NormalizedFilePath, + Range, + rangeToRealSrcSpan, + realSrcSpanToRange) +import qualified Development.IDE.Core.PositionMapping as P +import qualified Development.IDE.Core.Shake as IDE +import Development.IDE.GHC.Compat (RealSrcSpan, srcSpanFile) +import Development.IDE.GHC.Compat.Util (unpackFS) + + +------------------------------------------------------------------------------ +-- | A data kind for 'Tracked'. +data Age = Current | Stale Type + + +------------------------------------------------------------------------------ +-- | Some value, tagged with its age. All 'Current' ages are considered to be +-- the same thing, but 'Stale' values are protected by an untouchable variable +-- to ensure they can't be unified. +newtype Tracked (age :: Age) a = UnsafeTracked + { unTrack :: a + } + deriving stock (Functor, Foldable, Traversable) + deriving newtype (Eq, Ord, Show, Read, ToJSON, FromJSON, NFData) + deriving (Applicative, Monad) via Identity + + +------------------------------------------------------------------------------ +-- | Like 'P.PositionMapping', but with annotated ages for how 'Tracked' values +-- change. Use the 'Category' instance to compose 'PositionMapping's in order +-- to transform between values of different stale ages. +newtype PositionMap (from :: Age) (to :: Age) = PositionMap + { _getPositionMapping :: P.PositionMapping + } + +instance Category PositionMap where + id = coerce P.zeroMapping + (.) = coerce P.composeDelta + + +------------------------------------------------------------------------------ +-- | Get a 'PositionMap' that runs in the opposite direction. +dualPositionMap :: PositionMap from to -> PositionMap to from +dualPositionMap (PositionMap (P.PositionMapping (P.PositionDelta from to))) = + PositionMap $ P.PositionMapping $ P.PositionDelta to from + + +------------------------------------------------------------------------------ +-- | A pair containing a @'Tracked' 'Stale'@ value, as well as +-- a 'PositionMapping' that will fast-forward values to the current age. +data TrackedStale a where + TrackedStale + :: Tracked (Stale s) a + -> PositionMap (Stale s) Current + -> TrackedStale a + +instance Functor TrackedStale where + fmap f (TrackedStale t pm) = TrackedStale (fmap f t) pm + + +untrackedStaleValue :: TrackedStale a -> a +untrackedStaleValue (TrackedStale ta _) = coerce ta + + +------------------------------------------------------------------------------ +-- | A class for which 'Tracked' values can be run across a 'PositionMapping' +-- to change their ages. +class MapAge a where + {-# MINIMAL mapAgeFrom | mapAgeTo #-} + mapAgeFrom :: PositionMap from to -> Tracked to a -> Maybe (Tracked from a) + mapAgeFrom = mapAgeTo . dualPositionMap + + mapAgeTo :: PositionMap from to -> Tracked from a -> Maybe (Tracked to a) + mapAgeTo = mapAgeFrom . dualPositionMap + + +instance MapAge Range where + mapAgeFrom = coerce P.fromCurrentRange + mapAgeTo = coerce P.toCurrentRange + + +instance MapAge RealSrcSpan where + mapAgeFrom = + invMapAge (\fs -> rangeToRealSrcSpan (fromString $ unpackFS fs)) + (srcSpanFile &&& realSrcSpanToRange) + . mapAgeFrom + + +------------------------------------------------------------------------------ +-- | Helper function for deriving 'MapAge' for values in terms of other +-- instances. +invMapAge + :: (c -> a -> b) + -> (b -> (c, a)) + -> (Tracked from a -> Maybe (Tracked to a)) + -> Tracked from b + -> Maybe (Tracked to b) +invMapAge to from f t = + let (c, t') = unTrack $ fmap from t + in fmap (fmap $ to c) $ f $ UnsafeTracked t' + + +unsafeMkCurrent :: age -> Tracked 'Current age +unsafeMkCurrent = coerce + + +unsafeMkStale :: age -> Tracked (Stale s) age +unsafeMkStale = coerce + + +unsafeCopyAge :: Tracked age a -> b -> Tracked age b +unsafeCopyAge _ = coerce + + +-- | Request a Rule result, it not available return the last computed result, if any, which may be stale +useWithStale :: IdeRule k v + => k -> NormalizedFilePath -> Action (Maybe (TrackedStale v)) +useWithStale key file = do + x <- IDE.useWithStale key file + pure $ x <&> \(v, pm) -> + TrackedStale (coerce v) (coerce pm) + +-- | Request a Rule result, it not available return the last computed result which may be stale. +-- Errors out if none available. +useWithStale_ :: IdeRule k v + => k -> NormalizedFilePath -> Action (TrackedStale v) +useWithStale_ key file = do + (v, pm) <- IDE.useWithStale_ key file + pure $ TrackedStale (coerce v) (coerce pm) +
src/Development/IDE/GHC/CPP.hs view
@@ -1,63 +1,63 @@--- Copyright (c) 2019 The DAML Authors. All rights reserved.--- SPDX-License-Identifier: Apache-2.0--{-# LANGUAGE CPP #-}-{-# LANGUAGE NondecreasingIndentation #-}------------------------------------------------------------------------------------- GHC Driver------ (c) The University of Glasgow 2005-----------------------------------------------------------------------------------module Development.IDE.GHC.CPP(doCpp, addOptP)-where--import GHC-import Development.IDE.GHC.Compat as Compat-#if !MIN_VERSION_ghc(8,10,0)-import qualified Development.IDE.GHC.Compat.CPP as CPP-#else-import Development.IDE.GHC.Compat.Util-#endif--#if MIN_VERSION_ghc(9,0,0)-import qualified GHC.Driver.Pipeline as Pipeline-import GHC.Settings-#else-#if MIN_VERSION_ghc (8,10,0)-import qualified DriverPipeline as Pipeline-import ToolSettings-#else-import DynFlags-#endif-#endif--addOptP :: String -> DynFlags -> DynFlags-#if MIN_VERSION_ghc (8,10,0)-addOptP f = alterToolSettings $ \s -> s- { toolSettings_opt_P = f : toolSettings_opt_P s- , toolSettings_opt_P_fingerprint = fingerprintStrings (f : toolSettings_opt_P s)- }- where- fingerprintStrings ss = fingerprintFingerprints $ map fingerprintString ss- alterToolSettings f dynFlags = dynFlags { toolSettings = f (toolSettings dynFlags) }-#else-addOptP opt = onSettings (onOptP (opt:))- where- onSettings f x = x{settings = f $ settings x}- onOptP f x = x{sOpt_P = f $ sOpt_P x}-#endif--doCpp :: HscEnv -> Bool -> FilePath -> FilePath -> IO ()-doCpp env raw input_fn output_fn =-#if MIN_VERSION_ghc (9,2,0)- Pipeline.doCpp (hsc_logger env) (hsc_tmpfs env) (hsc_dflags env) (hsc_unit_env env) raw input_fn output_fn-#elif MIN_VERSION_ghc (8,10,0)- Pipeline.doCpp (hsc_dflags env) raw input_fn output_fn-#else- CPP.doCpp (hsc_dflags env) raw input_fn output_fn-#endif-+-- Copyright (c) 2019 The DAML Authors. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +{-# LANGUAGE CPP #-} +{-# LANGUAGE NondecreasingIndentation #-} + +----------------------------------------------------------------------------- +-- +-- GHC Driver +-- +-- (c) The University of Glasgow 2005 +-- +----------------------------------------------------------------------------- + +module Development.IDE.GHC.CPP(doCpp, addOptP) +where + +import Development.IDE.GHC.Compat as Compat +import GHC +#if !MIN_VERSION_ghc(8,10,0) +import qualified Development.IDE.GHC.Compat.CPP as CPP +#else +import Development.IDE.GHC.Compat.Util +#endif + +#if MIN_VERSION_ghc(9,0,0) +import qualified GHC.Driver.Pipeline as Pipeline +import GHC.Settings +#else +#if MIN_VERSION_ghc (8,10,0) +import qualified DriverPipeline as Pipeline +import ToolSettings +#else +import DynFlags +#endif +#endif + +addOptP :: String -> DynFlags -> DynFlags +#if MIN_VERSION_ghc (8,10,0) +addOptP f = alterToolSettings $ \s -> s + { toolSettings_opt_P = f : toolSettings_opt_P s + , toolSettings_opt_P_fingerprint = fingerprintStrings (f : toolSettings_opt_P s) + } + where + fingerprintStrings ss = fingerprintFingerprints $ map fingerprintString ss + alterToolSettings f dynFlags = dynFlags { toolSettings = f (toolSettings dynFlags) } +#else +addOptP opt = onSettings (onOptP (opt:)) + where + onSettings f x = x{settings = f $ settings x} + onOptP f x = x{sOpt_P = f $ sOpt_P x} +#endif + +doCpp :: HscEnv -> Bool -> FilePath -> FilePath -> IO () +doCpp env raw input_fn output_fn = +#if MIN_VERSION_ghc (9,2,0) + Pipeline.doCpp (hsc_logger env) (hsc_tmpfs env) (hsc_dflags env) (hsc_unit_env env) raw input_fn output_fn +#elif MIN_VERSION_ghc (8,10,0) + Pipeline.doCpp (hsc_dflags env) raw input_fn output_fn +#else + CPP.doCpp (hsc_dflags env) raw input_fn output_fn +#endif +
src/Development/IDE/GHC/Compat.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE PatternSynonyms #-} {-# OPTIONS -Wno-incomplete-uni-patterns -Wno-dodgy-imports #-} -- | Attempt at hiding the GHC version differences we can.@@ -15,13 +16,26 @@ setUpTypedHoles, upNameCache, disableWarningsAsErrors,+ reLoc,+ reLocA,+ getMessages',+ pattern PFailedWithErrorMessages, #if !MIN_VERSION_ghc(9,0,1) RefMap, #endif +#if MIN_VERSION_ghc(9,2,0)+ extendModSummaryNoDeps,+ emsModSummary,+#endif+ nodeInfo', getNodeIds,+ nodeInfoFromSource,+ isAnnotationInNodeInfo,+ mkAstNode,+ combineRealSrcSpans, isQualifiedImport, GhcVersion(..),@@ -43,6 +57,7 @@ -- * Compat modules module Development.IDE.GHC.Compat.Core, module Development.IDE.GHC.Compat.Env,+ module Development.IDE.GHC.Compat.ExactPrint, module Development.IDE.GHC.Compat.Iface, module Development.IDE.GHC.Compat.Logger, module Development.IDE.GHC.Compat.Outputable,@@ -56,65 +71,85 @@ runPp, ) where -import GHC hiding (HasSrcSpan, ModLocation, getLoc,- lookupName, RealSrcSpan)-import Development.IDE.GHC.Compat.Core-import Development.IDE.GHC.Compat.Env-import Development.IDE.GHC.Compat.Iface-import Development.IDE.GHC.Compat.Logger-import Development.IDE.GHC.Compat.Outputable-import Development.IDE.GHC.Compat.Parser-import Development.IDE.GHC.Compat.Plugins-import Development.IDE.GHC.Compat.Units-import Development.IDE.GHC.Compat.Util+import Development.IDE.GHC.Compat.Core+import Development.IDE.GHC.Compat.Env+import Development.IDE.GHC.Compat.ExactPrint+import Development.IDE.GHC.Compat.Iface+import Development.IDE.GHC.Compat.Logger+import Development.IDE.GHC.Compat.Outputable+import Development.IDE.GHC.Compat.Parser+import Development.IDE.GHC.Compat.Plugins+import Development.IDE.GHC.Compat.Units+import Development.IDE.GHC.Compat.Util+import GHC hiding (HasSrcSpan,+ ModLocation,+ RealSrcSpan, getLoc,+ lookupName) #if MIN_VERSION_ghc(9,0,0) import GHC.Data.StringBuffer-import GHC.Driver.Session hiding (ExposePackage)+import GHC.Driver.Session hiding (ExposePackage)+import qualified GHC.Types.SrcLoc as SrcLoc+import GHC.Utils.Error #if MIN_VERSION_ghc(9,2,0)-import GHC.Driver.Env as Env+import Data.Bifunctor+import GHC.Driver.Env as Env import GHC.Unit.Module.ModIface+import GHC.Unit.Module.ModSummary #else import GHC.Driver.Types #endif import GHC.Iface.Env-import GHC.Iface.Make (mkIfaceExports)-import qualified GHC.SysTools.Tasks as SysTools-import qualified GHC.Types.Avail as Avail+import GHC.Iface.Make (mkIfaceExports)+import qualified GHC.SysTools.Tasks as SysTools+import qualified GHC.Types.Avail as Avail #else-import DynFlags hiding (ExposePackage)-import HscTypes-import MkIface hiding (writeIfaceFile) import qualified Avail+import DynFlags hiding (ExposePackage)+import HscTypes+import MkIface hiding (writeIfaceFile) #if MIN_VERSION_ghc(8,8,0)-import StringBuffer (hPutStringBuffer)+import StringBuffer (hPutStringBuffer) #endif import qualified SysTools #if !MIN_VERSION_ghc(8,8,0)-import SrcLoc (RealLocated) import qualified EnumSet+import SrcLoc (RealLocated) import Foreign.ForeignPtr import System.IO #endif #endif -import Compat.HieAst (enrichHie)+import Compat.HieAst (enrichHie) import Compat.HieBin import Compat.HieTypes import Compat.HieUtils-import qualified Data.ByteString as BS+import qualified Data.ByteString as BS import Data.IORef -import qualified Data.Map as Map-import Data.List (foldl')+import Data.List (foldl')+import qualified Data.Map as Map+import qualified Data.Set as Set #if MIN_VERSION_ghc(9,0,0)-import qualified Data.Set as S+import qualified Data.Set as S #endif +#if !MIN_VERSION_ghc(8,10,0)+import Bag (unitBag)+#endif++#if !MIN_VERSION_ghc(9,2,0)+reLoc :: Located a -> Located a+reLoc = id++reLocA :: Located a -> Located a+reLocA = id+#endif+ #if !MIN_VERSION_ghc(8,8,0) hPutStringBuffer :: Handle -> StringBuffer -> IO () hPutStringBuffer hdl (StringBuffer buf len cur)@@ -122,12 +157,45 @@ hPutBuf hdl ptr len #endif +#if MIN_VERSION_ghc(9,2,0)+type ErrMsg = MsgEnvelope DecoratedSDoc+#endif++getMessages' :: PState -> DynFlags -> (Bag WarnMsg, Bag ErrMsg)+getMessages' pst dflags =+#if MIN_VERSION_ghc(9,2,0)+ bimap (fmap pprWarning) (fmap pprError) $+#endif+ getMessages pst+#if !MIN_VERSION_ghc(9,2,0)+ dflags+#endif++#if MIN_VERSION_ghc(9,2,0)+pattern PFailedWithErrorMessages :: forall a b. (b -> Bag (MsgEnvelope DecoratedSDoc)) -> ParseResult a+pattern PFailedWithErrorMessages msgs+ <- PFailed (const . fmap pprError . getErrorMessages -> msgs)+#elif MIN_VERSION_ghc(8,10,0)+pattern PFailedWithErrorMessages :: (DynFlags -> ErrorMessages) -> ParseResult a+pattern PFailedWithErrorMessages msgs+ <- PFailed (getErrorMessages -> msgs)+#else+pattern PFailedWithErrorMessages :: (DynFlags -> ErrorMessages) -> ParseResult a+pattern PFailedWithErrorMessages msgs+ <- ((fmap.fmap) unitBag . mkPlainErrMsgIfPFailed -> Just msgs)++mkPlainErrMsgIfPFailed (PFailed _ pst err) = Just (\dflags -> mkPlainErrMsg dflags pst err)+mkPlainErrMsgIfPFailed _ = Nothing+#endif+{-# COMPLETE PFailedWithErrorMessages #-}+ supportsHieFiles :: Bool supportsHieFiles = True hieExportNames :: HieFile -> [(SrcSpan, Name)] hieExportNames = nameListFromAvails . hie_exports + upNameCache :: IORef NameCache -> (NameCache -> (NameCache, c)) -> IO c #if MIN_VERSION_ghc(8,8,0) upNameCache = updNameCache@@ -274,6 +342,13 @@ -- unhelpfulSpanFS = id #endif +nodeInfoFromSource :: HieAST a -> Maybe (NodeInfo a)+#if MIN_VERSION_ghc(9,0,0)+nodeInfoFromSource = Map.lookup SourceInfo . getSourcedNodeInfo . sourcedNodeInfo+#else+nodeInfoFromSource = Just . nodeInfo+#endif+ data GhcVersion = GHC86 | GHC88@@ -312,4 +387,32 @@ SysTools.runPp #else const SysTools.runPp+#endif++isAnnotationInNodeInfo :: (FastString, FastString) -> NodeInfo a -> Bool+#if MIN_VERSION_ghc(9,2,0)+isAnnotationInNodeInfo (ctor, typ) = Set.member (NodeAnnotation ctor typ) . nodeAnnotations+#else+isAnnotationInNodeInfo p = Set.member p . nodeAnnotations+#endif++mkAstNode :: NodeInfo a -> Span -> [HieAST a] -> HieAST a+#if MIN_VERSION_ghc(9,0,0)+mkAstNode n = Node (SourcedNodeInfo $ Map.singleton GeneratedInfo n)+#else+mkAstNode = Node+#endif++combineRealSrcSpans :: RealSrcSpan -> RealSrcSpan -> RealSrcSpan+#if MIN_VERSION_ghc(9,2,0)+combineRealSrcSpans = SrcLoc.combineRealSrcSpans+#else+combineRealSrcSpans span1 span2+ = mkRealSrcSpan (mkRealSrcLoc file line_start col_start) (mkRealSrcLoc file line_end col_end)+ where+ (line_start, col_start) = min (srcSpanStartLine span1, srcSpanStartCol span1)+ (srcSpanStartLine span2, srcSpanStartCol span2)+ (line_end, col_end) = max (srcSpanEndLine span1, srcSpanEndCol span1)+ (srcSpanEndLine span2, srcSpanEndCol span2)+ file = srcSpanFile span1 #endif
src/Development/IDE/GHC/Compat/CPP.hs view
@@ -1,204 +1,204 @@--- Copyright (c) 2019 The DAML Authors. All rights reserved.--- SPDX-License-Identifier: Apache-2.0---- Copied from https://github.com/ghc/ghc/blob/master/compiler/main/DriverPipeline.hs on 14 May 2019--- Requested to be exposed at https://gitlab.haskell.org/ghc/ghc/merge_requests/944.--- Update the above MR got merged to master on 31 May 2019. When it becomes avialable to ghc-lib, this file can be removed.--{- HLINT ignore -} -- since copied from upstream--{-# LANGUAGE CPP #-}-{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}--- | Re-export 'doCpp' for GHC < 8.10.------ Later versions export what we need.-module Development.IDE.GHC.Compat.CPP (- doCpp- ) where--import FileCleanup-import Packages-import Panic-import SysTools-#if MIN_VERSION_ghc(8,8,2)-import LlvmCodeGen (llvmVersionList)-#elif MIN_VERSION_ghc(8,8,0)-import LlvmCodeGen (LlvmVersion (..))-#endif-import DynFlags-import Module (toInstalledUnitId, rtsUnitId)-import Control.Monad-import Data.List (intercalate)-import Data.Maybe-import Data.Version-import System.Directory-import System.FilePath-import System.Info--import Development.IDE.GHC.Compat as Compat--doCpp :: DynFlags -> Bool -> FilePath -> FilePath -> IO ()-doCpp dflags raw input_fn output_fn = do- let hscpp_opts = picPOpts dflags- let cmdline_include_paths = includePaths dflags-- pkg_include_dirs <- getPackageIncludePath dflags []- let include_paths_global = foldr (\ x xs -> ("-I" ++ x) : xs) []- (includePathsGlobal cmdline_include_paths ++ pkg_include_dirs)- let include_paths_quote = foldr (\ x xs -> ("-iquote" ++ x) : xs) []- (includePathsQuote cmdline_include_paths)- let include_paths = include_paths_quote ++ include_paths_global-- let verbFlags = getVerbFlags dflags-- let cpp_prog args | raw = SysTools.runCpp dflags args-#if MIN_VERSION_ghc(8,10,0)- | otherwise = SysTools.runCc Nothing-#else- | otherwise = SysTools.runCc-#endif- dflags (SysTools.Option "-E" : args)-- let target_defs =- -- NEIL: Patched to use System.Info instead of constants from CPP- [ "-D" ++ os ++ "_BUILD_OS",- "-D" ++ arch ++ "_BUILD_ARCH",- "-D" ++ os ++ "_HOST_OS",- "-D" ++ arch ++ "_HOST_ARCH" ]- -- remember, in code we *compile*, the HOST is the same our TARGET,- -- and BUILD is the same as our HOST.-- let sse_defs =- [ "-D__SSE__" | isSseEnabled dflags ] ++- [ "-D__SSE2__" | isSse2Enabled dflags ] ++- [ "-D__SSE4_2__" | isSse4_2Enabled dflags ]-- let avx_defs =- [ "-D__AVX__" | isAvxEnabled dflags ] ++- [ "-D__AVX2__" | isAvx2Enabled dflags ] ++- [ "-D__AVX512CD__" | isAvx512cdEnabled dflags ] ++- [ "-D__AVX512ER__" | isAvx512erEnabled dflags ] ++- [ "-D__AVX512F__" | isAvx512fEnabled dflags ] ++- [ "-D__AVX512PF__" | isAvx512pfEnabled dflags ]-- backend_defs <- getBackendDefs dflags-- let th_defs = [ "-D__GLASGOW_HASKELL_TH__" ]- -- Default CPP defines in Haskell source- ghcVersionH <- getGhcVersionPathName dflags- let hsSourceCppOpts = [ "-include", ghcVersionH ]-- -- MIN_VERSION macros- let uids = explicitPackages (pkgState dflags)- pkgs = catMaybes (map (lookupPackage dflags) uids)- mb_macro_include <-- if not (null pkgs) && gopt Opt_VersionMacros dflags- then do macro_stub <- newTempName dflags TFL_CurrentModule "h"- writeFile macro_stub (generatePackageVersionMacros pkgs)- -- Include version macros for every *exposed* package.- -- Without -hide-all-packages and with a package database- -- size of 1000 packages, it takes cpp an estimated 2- -- milliseconds to process this file. See #10970- -- comment 8.- return [SysTools.FileOption "-include" macro_stub]- else return []-- cpp_prog ( map SysTools.Option verbFlags- ++ map SysTools.Option include_paths- ++ map SysTools.Option hsSourceCppOpts- ++ map SysTools.Option target_defs- ++ map SysTools.Option backend_defs- ++ map SysTools.Option th_defs- ++ map SysTools.Option hscpp_opts- ++ map SysTools.Option sse_defs- ++ map SysTools.Option avx_defs- ++ mb_macro_include- -- Set the language mode to assembler-with-cpp when preprocessing. This- -- alleviates some of the C99 macro rules relating to whitespace and the hash- -- operator, which we tend to abuse. Clang in particular is not very happy- -- about this.- ++ [ SysTools.Option "-x"- , SysTools.Option "assembler-with-cpp"- , SysTools.Option input_fn- -- We hackily use Option instead of FileOption here, so that the file- -- name is not back-slashed on Windows. cpp is capable of- -- dealing with / in filenames, so it works fine. Furthermore- -- if we put in backslashes, cpp outputs #line directives- -- with *double* backslashes. And that in turn means that- -- our error messages get double backslashes in them.- -- In due course we should arrange that the lexer deals- -- with these \\ escapes properly.- , SysTools.Option "-o"- , SysTools.FileOption "" output_fn- ])--getBackendDefs :: DynFlags -> IO [String]-getBackendDefs dflags | hscTarget dflags == HscLlvm = do- llvmVer <- figureLlvmVersion dflags- return $ case llvmVer of-#if MIN_VERSION_ghc(8,8,2)- Just v- | [m] <- llvmVersionList v -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m, 0) ]- | m:n:_ <- llvmVersionList v -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m, n) ]-#elif MIN_VERSION_ghc(8,8,0)- Just (LlvmVersion n) -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (n,0) ]- Just (LlvmVersionOld m n) -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m,n) ]-#else- Just n -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format n ]-#endif- _ -> []- where- format (major, minor)- | minor >= 100 = error "getBackendDefs: Unsupported minor version"- | otherwise = show $ (100 * major + minor :: Int) -- Contract is Int--getBackendDefs _ =- return []---- ------------------------------------------------------------------------------ Macros (cribbed from Cabal)--generatePackageVersionMacros :: [Compat.UnitInfo] -> String-generatePackageVersionMacros pkgs = concat- -- Do not add any C-style comments. See #3389.- [ generateMacros "" pkgname version- | pkg <- pkgs- , let version = packageVersion pkg- pkgname = map fixchar (packageNameString pkg)- ]--fixchar :: Char -> Char-fixchar '-' = '_'-fixchar c = c--generateMacros :: String -> String -> Version -> String-generateMacros prefix name version =- concat- ["#define ", prefix, "VERSION_",name," ",show (showVersion version),"\n"- ,"#define MIN_", prefix, "VERSION_",name,"(major1,major2,minor) (\\\n"- ," (major1) < ",major1," || \\\n"- ," (major1) == ",major1," && (major2) < ",major2," || \\\n"- ," (major1) == ",major1," && (major2) == ",major2," && (minor) <= ",minor,")"- ,"\n\n"- ]- where- (major1:major2:minor:_) = map show (versionBranch version ++ repeat 0)----- | Find out path to @ghcversion.h@ file-getGhcVersionPathName :: DynFlags -> IO FilePath-getGhcVersionPathName dflags = do- candidates <- case ghcVersionFile dflags of- Just path -> return [path]- Nothing -> (map (</> "ghcversion.h")) <$>- (getPackageIncludePath dflags [toInstalledUnitId rtsUnit])-- found <- filterM doesFileExist candidates- case found of- [] -> throwGhcExceptionIO (InstallationError- ("ghcversion.h missing; tried: "- ++ intercalate ", " candidates))- (x:_) -> return x--rtsUnit :: UnitId-rtsUnit = Module.rtsUnitId+-- Copyright (c) 2019 The DAML Authors. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +-- Copied from https://github.com/ghc/ghc/blob/master/compiler/main/DriverPipeline.hs on 14 May 2019 +-- Requested to be exposed at https://gitlab.haskell.org/ghc/ghc/merge_requests/944. +-- Update the above MR got merged to master on 31 May 2019. When it becomes avialable to ghc-lib, this file can be removed. + +{- HLINT ignore -} -- since copied from upstream + +{-# LANGUAGE CPP #-} +{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} +-- | Re-export 'doCpp' for GHC < 8.10. +-- +-- Later versions export what we need. +module Development.IDE.GHC.Compat.CPP ( + doCpp + ) where + +import FileCleanup +import Packages +import Panic +import SysTools +#if MIN_VERSION_ghc(8,8,2) +import LlvmCodeGen (llvmVersionList) +#elif MIN_VERSION_ghc(8,8,0) +import LlvmCodeGen (LlvmVersion (..)) +#endif +import Control.Monad +import Data.List (intercalate) +import Data.Maybe +import Data.Version +import DynFlags +import Module (rtsUnitId, toInstalledUnitId) +import System.Directory +import System.FilePath +import System.Info + +import Development.IDE.GHC.Compat as Compat + +doCpp :: DynFlags -> Bool -> FilePath -> FilePath -> IO () +doCpp dflags raw input_fn output_fn = do + let hscpp_opts = picPOpts dflags + let cmdline_include_paths = includePaths dflags + + pkg_include_dirs <- getPackageIncludePath dflags [] + let include_paths_global = foldr (\ x xs -> ("-I" ++ x) : xs) [] + (includePathsGlobal cmdline_include_paths ++ pkg_include_dirs) + let include_paths_quote = foldr (\ x xs -> ("-iquote" ++ x) : xs) [] + (includePathsQuote cmdline_include_paths) + let include_paths = include_paths_quote ++ include_paths_global + + let verbFlags = getVerbFlags dflags + + let cpp_prog args | raw = SysTools.runCpp dflags args +#if MIN_VERSION_ghc(8,10,0) + | otherwise = SysTools.runCc Nothing +#else + | otherwise = SysTools.runCc +#endif + dflags (SysTools.Option "-E" : args) + + let target_defs = + -- NEIL: Patched to use System.Info instead of constants from CPP + [ "-D" ++ os ++ "_BUILD_OS", + "-D" ++ arch ++ "_BUILD_ARCH", + "-D" ++ os ++ "_HOST_OS", + "-D" ++ arch ++ "_HOST_ARCH" ] + -- remember, in code we *compile*, the HOST is the same our TARGET, + -- and BUILD is the same as our HOST. + + let sse_defs = + [ "-D__SSE__" | isSseEnabled dflags ] ++ + [ "-D__SSE2__" | isSse2Enabled dflags ] ++ + [ "-D__SSE4_2__" | isSse4_2Enabled dflags ] + + let avx_defs = + [ "-D__AVX__" | isAvxEnabled dflags ] ++ + [ "-D__AVX2__" | isAvx2Enabled dflags ] ++ + [ "-D__AVX512CD__" | isAvx512cdEnabled dflags ] ++ + [ "-D__AVX512ER__" | isAvx512erEnabled dflags ] ++ + [ "-D__AVX512F__" | isAvx512fEnabled dflags ] ++ + [ "-D__AVX512PF__" | isAvx512pfEnabled dflags ] + + backend_defs <- getBackendDefs dflags + + let th_defs = [ "-D__GLASGOW_HASKELL_TH__" ] + -- Default CPP defines in Haskell source + ghcVersionH <- getGhcVersionPathName dflags + let hsSourceCppOpts = [ "-include", ghcVersionH ] + + -- MIN_VERSION macros + let uids = explicitPackages (pkgState dflags) + pkgs = catMaybes (map (lookupPackage dflags) uids) + mb_macro_include <- + if not (null pkgs) && gopt Opt_VersionMacros dflags + then do macro_stub <- newTempName dflags TFL_CurrentModule "h" + writeFile macro_stub (generatePackageVersionMacros pkgs) + -- Include version macros for every *exposed* package. + -- Without -hide-all-packages and with a package database + -- size of 1000 packages, it takes cpp an estimated 2 + -- milliseconds to process this file. See #10970 + -- comment 8. + return [SysTools.FileOption "-include" macro_stub] + else return [] + + cpp_prog ( map SysTools.Option verbFlags + ++ map SysTools.Option include_paths + ++ map SysTools.Option hsSourceCppOpts + ++ map SysTools.Option target_defs + ++ map SysTools.Option backend_defs + ++ map SysTools.Option th_defs + ++ map SysTools.Option hscpp_opts + ++ map SysTools.Option sse_defs + ++ map SysTools.Option avx_defs + ++ mb_macro_include + -- Set the language mode to assembler-with-cpp when preprocessing. This + -- alleviates some of the C99 macro rules relating to whitespace and the hash + -- operator, which we tend to abuse. Clang in particular is not very happy + -- about this. + ++ [ SysTools.Option "-x" + , SysTools.Option "assembler-with-cpp" + , SysTools.Option input_fn + -- We hackily use Option instead of FileOption here, so that the file + -- name is not back-slashed on Windows. cpp is capable of + -- dealing with / in filenames, so it works fine. Furthermore + -- if we put in backslashes, cpp outputs #line directives + -- with *double* backslashes. And that in turn means that + -- our error messages get double backslashes in them. + -- In due course we should arrange that the lexer deals + -- with these \\ escapes properly. + , SysTools.Option "-o" + , SysTools.FileOption "" output_fn + ]) + +getBackendDefs :: DynFlags -> IO [String] +getBackendDefs dflags | hscTarget dflags == HscLlvm = do + llvmVer <- figureLlvmVersion dflags + return $ case llvmVer of +#if MIN_VERSION_ghc(8,8,2) + Just v + | [m] <- llvmVersionList v -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m, 0) ] + | m:n:_ <- llvmVersionList v -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m, n) ] +#elif MIN_VERSION_ghc(8,8,0) + Just (LlvmVersion n) -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (n,0) ] + Just (LlvmVersionOld m n) -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m,n) ] +#else + Just n -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format n ] +#endif + _ -> [] + where + format (major, minor) + | minor >= 100 = error "getBackendDefs: Unsupported minor version" + | otherwise = show $ (100 * major + minor :: Int) -- Contract is Int + +getBackendDefs _ = + return [] + +-- --------------------------------------------------------------------------- +-- Macros (cribbed from Cabal) + +generatePackageVersionMacros :: [Compat.UnitInfo] -> String +generatePackageVersionMacros pkgs = concat + -- Do not add any C-style comments. See #3389. + [ generateMacros "" pkgname version + | pkg <- pkgs + , let version = packageVersion pkg + pkgname = map fixchar (packageNameString pkg) + ] + +fixchar :: Char -> Char +fixchar '-' = '_' +fixchar c = c + +generateMacros :: String -> String -> Version -> String +generateMacros prefix name version = + concat + ["#define ", prefix, "VERSION_",name," ",show (showVersion version),"\n" + ,"#define MIN_", prefix, "VERSION_",name,"(major1,major2,minor) (\\\n" + ," (major1) < ",major1," || \\\n" + ," (major1) == ",major1," && (major2) < ",major2," || \\\n" + ," (major1) == ",major1," && (major2) == ",major2," && (minor) <= ",minor,")" + ,"\n\n" + ] + where + (major1:major2:minor:_) = map show (versionBranch version ++ repeat 0) + + +-- | Find out path to @ghcversion.h@ file +getGhcVersionPathName :: DynFlags -> IO FilePath +getGhcVersionPathName dflags = do + candidates <- case ghcVersionFile dflags of + Just path -> return [path] + Nothing -> (map (</> "ghcversion.h")) <$> + (getPackageIncludePath dflags [toInstalledUnitId rtsUnit]) + + found <- filterM doesFileExist candidates + case found of + [] -> throwGhcExceptionIO (InstallationError + ("ghcversion.h missing; tried: " + ++ intercalate ", " candidates)) + (x:_) -> return x + +rtsUnit :: UnitId +rtsUnit = Module.rtsUnitId
src/Development/IDE/GHC/Compat/Core.hs view
@@ -34,6 +34,7 @@ refLevelHoleFits, maxRefHoleFits, maxValidHoleFits,+ setOutputFile, #if MIN_VERSION_ghc(8,8,0) CommandLineOption, #if !MIN_VERSION_ghc(9,2,0)@@ -66,7 +67,10 @@ -- slightly unsafe setUnsafeGlobalDynFlags, -- * Linear Haskell+#if !MIN_VERSION_ghc(9,0,0) Scaled,+ unrestricted,+#endif scaledThing, -- * Interface Files IfaceExport,@@ -107,6 +111,11 @@ CgGuts(..), -- * ModDetails ModDetails(..),+ -- * HsExpr,+#if !MIN_VERSION_ghc(9,2,0)+ pattern HsLet,+ pattern LetStmt,+#endif -- * Var Type ( TyCoRep.TyVarTy,@@ -121,7 +130,10 @@ TyCoRep.CoercionTy ), pattern FunTy,+ pattern ConPatIn,+#if !MIN_VERSION_ghc(9,2,0) Development.IDE.GHC.Compat.Core.splitForAllTyCoVars,+#endif Development.IDE.GHC.Compat.Core.mkVisFunTys, Development.IDE.GHC.Compat.Core.mkInfForAllTys, -- * Specs@@ -147,8 +159,7 @@ -- * TcGblEnv TcGblEnv(..), -- * Parsing and LExer types- HsParsedModule(..),- GHC.ParsedModule(..),+ HsModule(..), GHC.ParsedSource, GHC.RenamedSource, -- * Compilation Main@@ -184,6 +195,17 @@ SrcLoc.Located, SrcLoc.unLoc, getLoc,+ getLocA,+ locA,+ noLocA,+ LocatedAn,+#if MIN_VERSION_ghc(9,2,0)+ GHC.AnnListItem(..),+ GHC.NameAnn(..),+#else+ AnnListItem,+ NameAnn,+#endif SrcLoc.RealLocated, SrcLoc.GenLocated(..), SrcLoc.SrcSpan(SrcLoc.UnhelpfulSpan),@@ -193,6 +215,10 @@ pattern RealSrcLoc, SrcLoc.SrcLoc(SrcLoc.UnhelpfulLoc), BufSpan,+#if MIN_VERSION_ghc(9,2,0)+ SrcSpanAnn',+ GHC.SrcAnn,+#endif SrcLoc.leftmost_smallest, SrcLoc.containsSpan, SrcLoc.mkGeneralSrcSpan,@@ -202,6 +228,7 @@ SrcLoc.realSrcLocSpan, SrcLoc.realSrcSpanStart, SrcLoc.realSrcSpanEnd,+ isSubspanOfA, SrcLoc.isSubspanOf, SrcLoc.wiredInSrcSpan, SrcLoc.mkSrcSpan,@@ -275,6 +302,17 @@ -- * Panic PlainGhcException, panic,+ -- * Other+ GHC.CoreModule(..),+ GHC.SafeHaskellMode(..),+ pattern GRE,+ gre_name,+ gre_imp,+ gre_lcl,+ gre_par,+#if MIN_VERSION_ghc(9,2,0)+ collectHsBindsBinders,+#endif -- * Util Module re-exports #if MIN_VERSION_ghc(9,0,0) module GHC.Builtin.Names,@@ -287,6 +325,7 @@ module GHC.Core.DataCon, module GHC.Core.FamInstEnv, module GHC.Core.InstEnv,+ module GHC.Types.Unique.FM, #if !MIN_VERSION_ghc(9,2,0) module GHC.Core.Ppr.TyThing, #endif@@ -306,7 +345,15 @@ module GHC.Iface.Syntax, #if MIN_VERSION_ghc(9,2,0)- module Language.Haskell.Syntax.Expr,+ module GHC.Hs.Decls,+ module GHC.Hs.Expr,+ module GHC.Hs.Doc,+ module GHC.Hs.Extension,+ module GHC.Hs.ImpExp,+ module GHC.Hs.Pat,+ module GHC.Hs.Type,+ module GHC.Hs.Utils,+ module Language.Haskell.Syntax, #endif module GHC.Rename.Names,@@ -328,6 +375,7 @@ module GHC.Types.Name.Env, module GHC.Types.Name.Reader, #if MIN_VERSION_ghc(9,2,0)+ module GHC.Types.Avail, module GHC.Types.SourceFile, module GHC.Types.SourceText, module GHC.Types.TyThing,@@ -380,6 +428,7 @@ module TysWiredIn, module Type, module Unify,+ module UniqFM, module UniqSupply, module Var, #endif@@ -415,32 +464,36 @@ import qualified GHC #if MIN_VERSION_ghc(9,0,0)-import GHC.Builtin.Names hiding (Unique, printName)+import GHC.Builtin.Names hiding (Unique, printName) import GHC.Builtin.Types import GHC.Builtin.Types.Prim import GHC.Builtin.Utils import GHC.Core.Class import GHC.Core.Coercion import GHC.Core.ConLike-import GHC.Core.DataCon hiding (dataConExTyCoVars)-import qualified GHC.Core.DataCon as DataCon-import GHC.Core.FamInstEnv+import GHC.Core.DataCon hiding (dataConExTyCoVars)+import qualified GHC.Core.DataCon as DataCon+import GHC.Core.FamInstEnv hiding (pprFamInst) import GHC.Core.InstEnv+import GHC.Types.Unique.FM #if MIN_VERSION_ghc(9,2,0)-import GHC.Core.Multiplicity (scaledThing)+import GHC.Data.Bag+import GHC.Core.Multiplicity (scaledThing) #else-import GHC.Core.Ppr.TyThing hiding (pprFamInst)-import GHC.Core.TyCo.Rep (scaledThing)+import GHC.Core.Ppr.TyThing hiding (pprFamInst)+import GHC.Core.TyCo.Rep (scaledThing) #endif import GHC.Core.PatSyn import GHC.Core.Predicate import GHC.Core.TyCo.Ppr-import qualified GHC.Core.TyCo.Rep as TyCoRep+import qualified GHC.Core.TyCo.Rep as TyCoRep import GHC.Core.TyCon-import GHC.Core.Type hiding (mkInfForAllTys, mkVisFunTys)+import GHC.Core.Type hiding (mkInfForAllTys,+ mkVisFunTys) import GHC.Core.Unify import GHC.Core.Utils + #if MIN_VERSION_ghc(9,2,0) import GHC.Driver.Env #else@@ -448,120 +501,147 @@ import GHC.Driver.Types import GHC.Driver.Ways #endif-import GHC.Driver.CmdLine (Warn (..))+import GHC.Driver.CmdLine (Warn (..)) import GHC.Driver.Hooks import GHC.Driver.Main import GHC.Driver.Monad import GHC.Driver.Phases import GHC.Driver.Pipeline import GHC.Driver.Plugins-import GHC.Driver.Session hiding (ExposePackage)-import qualified GHC.Driver.Session as DynFlags+import GHC.Driver.Session hiding (ExposePackage)+import qualified GHC.Driver.Session as DynFlags+#if MIN_VERSION_ghc(9,2,0)+import GHC.Hs (HsModule (..), SrcSpanAnn')+import GHC.Hs.Decls hiding (FunDep)+import GHC.Hs.Doc+import GHC.Hs.Expr+import GHC.Hs.Extension+import GHC.Hs.ImpExp+import GHC.Hs.Pat+import GHC.Hs.Type+import GHC.Hs.Utils hiding (collectHsBindsBinders)+import qualified GHC.Hs.Utils as GHC+#endif #if !MIN_VERSION_ghc(9,2,0)-import GHC.Hs+import GHC.Hs hiding (HsLet, LetStmt) #endif import GHC.HsToCore.Docs import GHC.HsToCore.Expr import GHC.HsToCore.Monad import GHC.Iface.Load-import GHC.Iface.Make (mkFullIface, mkIfaceTc,- mkPartialIface)+import GHC.Iface.Make (mkFullIface, mkIfaceTc,+ mkPartialIface) import GHC.Iface.Recomp import GHC.Iface.Syntax import GHC.Iface.Tidy import GHC.IfaceToCore import GHC.Parser-import GHC.Parser.Header hiding (getImports)-import GHC.Parser.Lexer+import GHC.Parser.Header hiding (getImports) #if MIN_VERSION_ghc(9,2,0)-import GHC.Linker.Loader+import qualified GHC.Linker.Loader as Linker import GHC.Linker.Types+import GHC.Parser.Lexer hiding (initParserState)+import GHC.Parser.Annotation (EpAnn (..)) import GHC.Platform.Ways+import GHC.Runtime.Context (InteractiveImport (..)) #else-import GHC.Runtime.Linker+import GHC.Parser.Lexer+import qualified GHC.Runtime.Linker as Linker #endif import GHC.Rename.Names import GHC.Rename.Splice-import GHC.Runtime.Interpreter+import qualified GHC.Runtime.Interpreter as GHCi import GHC.Tc.Instance.Family import GHC.Tc.Module import GHC.Tc.Types-import GHC.Tc.Types.Evidence hiding ((<.>))+import GHC.Tc.Types.Evidence hiding ((<.>)) import GHC.Tc.Utils.Env-import GHC.Tc.Utils.Monad hiding (Applicative (..), IORef,- MonadFix (..), MonadIO (..), allM,- anyM, concatMapM, mapMaybeM, (<$>))-import GHC.Tc.Utils.TcType as TcType-import qualified GHC.Types.Avail as Avail+import GHC.Tc.Utils.Monad hiding (Applicative (..), IORef,+ MonadFix (..), MonadIO (..),+ allM, anyM, concatMapM,+ mapMaybeM, (<$>))+import GHC.Tc.Utils.TcType as TcType+import qualified GHC.Types.Avail as Avail #if MIN_VERSION_ghc(9,2,0)+import GHC.Types.Avail (greNamePrintableName)+import GHC.Types.Fixity (LexicalFixity (..))+#endif+#if MIN_VERSION_ghc(9,2,0) import GHC.Types.Meta #endif import GHC.Types.Basic import GHC.Types.Id-import GHC.Types.Name hiding (varName)+import GHC.Types.Name hiding (varName) import GHC.Types.Name.Cache import GHC.Types.Name.Env-import GHC.Types.Name.Reader+import GHC.Types.Name.Reader hiding (GRE, gre_name, gre_imp, gre_lcl, gre_par)+import qualified GHC.Types.Name.Reader as RdrName #if MIN_VERSION_ghc(9,2,0) import GHC.Types.Name.Set-import GHC.Types.SourceFile (HscSource (..),- SourceModified (..))+import GHC.Types.SourceFile (HscSource (..),+ SourceModified (..)) import GHC.Types.SourceText+import GHC.Types.Target (Target (..), TargetId (..)) import GHC.Types.TyThing import GHC.Types.TyThing.Ppr #else import GHC.Types.Name.Set #endif-import GHC.Types.SrcLoc (BufPos, BufSpan, SrcSpan (UnhelpfulSpan), SrcLoc(UnhelpfulLoc))-import qualified GHC.Types.SrcLoc as SrcLoc+import GHC.Types.SrcLoc (BufPos, BufSpan,+ SrcLoc (UnhelpfulLoc),+ SrcSpan (UnhelpfulSpan))+import qualified GHC.Types.SrcLoc as SrcLoc import GHC.Types.Unique.Supply-import GHC.Types.Var (Var (varName), setTyVarUnique,- setVarUnique)+import GHC.Types.Var (Var (varName), setTyVarUnique,+ setVarUnique) #if MIN_VERSION_ghc(9,2,0) import GHC.Unit.Finder import GHC.Unit.Home.ModInfo #endif-import GHC.Unit.Info (PackageName (..))-import GHC.Unit.Module hiding (ModLocation (..), UnitId,- addBootSuffixLocnOut, moduleUnit,- toUnitId)-import qualified GHC.Unit.Module as Module+import GHC.Unit.Info (PackageName (..))+import GHC.Unit.Module hiding (ModLocation (..), UnitId,+ addBootSuffixLocnOut, moduleUnit,+ toUnitId)+import qualified GHC.Unit.Module as Module #if MIN_VERSION_ghc(9,2,0)+import GHC.Unit.Module.Graph (mkModuleGraph) import GHC.Unit.Module.Imported import GHC.Unit.Module.ModDetails import GHC.Unit.Module.ModGuts-import GHC.Unit.Module.ModIface (IfaceExport)+import GHC.Unit.Module.ModIface (IfaceExport, ModIface (..),+ ModIface_ (..))+import GHC.Unit.Module.ModSummary (ModSummary (..)) #endif-import GHC.Unit.State (ModuleOrigin (..))-import GHC.Utils.Error (Severity (..))-import GHC.Utils.Panic hiding (try)-import qualified GHC.Utils.Panic.Plain as Plain+import GHC.Unit.State (ModuleOrigin (..))+import GHC.Utils.Error (Severity (..))+import GHC.Utils.Panic hiding (try)+import qualified GHC.Utils.Panic.Plain as Plain #else import qualified Avail-import BasicTypes hiding (Version)+import BasicTypes hiding (Version) import Class-import CmdLineParser (Warn (..))+import CmdLineParser (Warn (..)) import ConLike import CoreUtils-import DataCon hiding (dataConExTyCoVars)+import DataCon hiding (dataConExTyCoVars) import qualified DataCon import DriverPhases import DriverPipeline import DsExpr-import DsMonad hiding (foldrM)-import DynFlags hiding (ExposePackage)+import DsMonad hiding (foldrM)+import DynFlags hiding (ExposePackage) import qualified DynFlags-import ErrUtils hiding (logInfo, mkWarnMsg)+import ErrUtils hiding (logInfo, mkWarnMsg) import ExtractDocs import FamInst import FamInstEnv import Finder #if MIN_VERSION_ghc(8,10,0)-import GHC.Hs+import GHC.Hs hiding (HsLet, LetStmt) #endif-import GHCi+import qualified GHCi import GhcMonad-import HeaderInfo hiding (getImports)+import HeaderInfo hiding (getImports) import Hooks import HscMain import HscTypes@@ -570,87 +650,97 @@ import HsBinds import HsDecls import HsDoc-import HsExpr+import HsExpr hiding (HsLet, LetStmt) import HsExtension import HsImpExp import HsLit import HsPat-import HsSyn hiding (wildCardName)-import HsTypes hiding (wildCardName)+import HsSyn hiding (wildCardName, HsLet, LetStmt)+import HsTypes hiding (wildCardName) import HsUtils #endif import Id import IfaceSyn import InstEnv-import Lexer hiding (getSrcLoc)-import Linker+import Lexer hiding (getSrcLoc)+import qualified Linker import LoadIface import MkIface-import Module hiding (ModLocation (..), UnitId,- addBootSuffixLocnOut, moduleUnitId)+import Module hiding (ModLocation (..), UnitId,+ addBootSuffixLocnOut,+ moduleUnitId) import qualified Module-import Name hiding (varName)+import Name hiding (varName) import NameCache import NameEnv import NameSet import Packages #if MIN_VERSION_ghc(8,8,0)-import Panic hiding (try)-import qualified PlainPanic as Plain+import Panic hiding (try)+import qualified PlainPanic as Plain #else-import Panic hiding (GhcException, try)-import qualified Panic as Plain+import Panic hiding (GhcException, try)+import qualified Panic as Plain #endif import Parser import PatSyn #if MIN_VERSION_ghc(8,8,0) import Plugins #endif-import PprTyThing hiding (pprFamInst)+import PprTyThing hiding (pprFamInst) import PrelInfo-import PrelNames hiding (Unique, printName)-import RdrName+import PrelNames hiding (Unique, printName)+import RdrName hiding (GRE, gre_name, gre_imp, gre_lcl, gre_par)+import qualified RdrName import RnNames import RnSplice import qualified SrcLoc import TcEnv-import TcEvidence hiding ((<.>))+import TcEvidence hiding ((<.>)) import TcIface import TcRnDriver-import TcRnMonad hiding (Applicative (..), IORef,- MonadFix (..), MonadIO (..), allM,- anyM, concatMapM, foldrM,- mapMaybeM, (<$>))+import TcRnMonad hiding (Applicative (..), IORef,+ MonadFix (..), MonadIO (..),+ allM, anyM, concatMapM, foldrM,+ mapMaybeM, (<$>)) import TcRnTypes-import TcType hiding (mkVisFunTys)+import TcType hiding (mkVisFunTys) import qualified TcType import TidyPgm import qualified TyCoRep import TyCon-import Type hiding (mkVisFunTys)+import Type hiding (mkVisFunTys) import TysPrim import TysWiredIn import Unify+import UniqFM import UniqSupply-import Var (Var (varName), setTyVarUnique,- setVarUnique, varType)+import Var (Var (varName), setTyVarUnique,+ setVarUnique, varType) #if MIN_VERSION_ghc(8,10,0)-import Coercion (coercionKind)+import Coercion (coercionKind) import Predicate-import SrcLoc (SrcSpan (UnhelpfulSpan), SrcLoc (UnhelpfulLoc))+import SrcLoc (Located, SrcLoc (UnhelpfulLoc),+ SrcSpan (UnhelpfulSpan)) #else-import SrcLoc (RealLocated,- SrcSpan (UnhelpfulSpan),- SrcLoc (UnhelpfulLoc))+import SrcLoc (RealLocated,+ SrcLoc (UnhelpfulLoc),+ SrcSpan (UnhelpfulSpan)) #endif #endif + #if !MIN_VERSION_ghc(8,8,0)-import Data.List (isSuffixOf)+import Data.List (isSuffixOf) import System.FilePath #endif ++#if MIN_VERSION_ghc(9,2,0)+import Language.Haskell.Syntax hiding (FunDep)+#endif+ #if !MIN_VERSION_ghc(9,0,0) type BufSpan = () type BufPos = ()@@ -726,12 +816,23 @@ class HasSrcSpan a where getLoc :: a -> SrcSpan +instance HasSrcSpan SrcSpan where+ getLoc = id+ instance HasSrcSpan (SrcLoc.GenLocated SrcSpan a) where getLoc = GHC.getLoc --- getLoc :: GenLocated l a -> l--- getLoc = GHC.getLoc+#if MIN_VERSION_ghc(9,2,0)+instance HasSrcSpan (SrcSpanAnn' ann) where+ getLoc = locA+instance HasSrcSpan (SrcLoc.GenLocated (SrcSpanAnn' ann) a) where+ getLoc (L l _) = l +pattern L :: HasSrcSpan a => SrcSpan -> e -> SrcLoc.GenLocated a e+pattern L l a <- GHC.L (getLoc -> l) a+{-# COMPLETE L #-}+#endif+ #elif MIN_VERSION_ghc(8,8,0) type HasSrcSpan = SrcLoc.HasSrcSpan getLoc :: SrcLoc.HasSrcSpan a => a -> SrcLoc.SrcSpan@@ -781,6 +882,9 @@ type Scaled a = a scaledThing :: Scaled a -> a scaledThing = id++unrestricted :: a -> Scaled a+unrestricted = id #endif mkVisFunTys :: [Scaled Type] -> Type -> Type@@ -799,11 +903,9 @@ mkInvForAllTys #endif +#if !MIN_VERSION_ghc(9,2,0) splitForAllTyCoVars :: Type -> ([TyCoVar], Type) splitForAllTyCoVars =-#if MIN_VERSION_ghc(9,2,0)- TcType.splitForAllTyCoVars-#else splitForAllTys #endif @@ -857,4 +959,120 @@ type PlainGhcException = Plain.PlainGhcException #else type PlainGhcException = Plain.GhcException+#endif++#if MIN_VERSION_ghc(9,0,0)+-- This is from the old api, but it still simplifies+pattern ConPatIn :: SrcLoc.Located (ConLikeP GhcPs) -> HsConPatDetails GhcPs -> Pat GhcPs+#if MIN_VERSION_ghc(9,2,0)+pattern ConPatIn con args <- ConPat EpAnnNotUsed (L _ (SrcLoc.noLoc -> con)) args+ where+ ConPatIn con args = ConPat EpAnnNotUsed (GHC.noLocA $ SrcLoc.unLoc con) args+#else+pattern ConPatIn con args = ConPat NoExtField con args+#endif+#endif++initDynLinker, initObjLinker :: HscEnv -> IO ()+initDynLinker =+#if !MIN_VERSION_ghc(9,0,0)+ Linker.initDynLinker+#else+ -- It errors out in GHC 9.0 and doesn't exist in 9.2+ const $ return ()+#endif++initObjLinker env =+#if !MIN_VERSION_ghc(9,2,0)+ GHCi.initObjLinker env+#else+ GHCi.initObjLinker (GHCi.hscInterp env)+#endif++loadDLL :: HscEnv -> String -> IO (Maybe String)+loadDLL env =+#if !MIN_VERSION_ghc(9,2,0)+ GHCi.loadDLL env+#else+ GHCi.loadDLL (GHCi.hscInterp env)+#endif++unload :: HscEnv -> [Linkable] -> IO ()+unload hsc_env linkables =+ Linker.unload+#if MIN_VERSION_ghc(9,2,0)+ (GHCi.hscInterp hsc_env)+#endif+ hsc_env linkables++setOutputFile :: FilePath -> DynFlags -> DynFlags+setOutputFile f d = d {+#if MIN_VERSION_ghc(9,2,0)+ outputFile_ = Just f+#else+ outputFile = Just f+#endif+ }++isSubspanOfA :: LocatedAn la a -> LocatedAn lb b -> Bool+#if MIN_VERSION_ghc(9,2,0)+isSubspanOfA a b = SrcLoc.isSubspanOf (GHC.getLocA a) (GHC.getLocA b)+#else+isSubspanOfA a b = SrcLoc.isSubspanOf (GHC.getLoc a) (GHC.getLoc b)+#endif++#if MIN_VERSION_ghc(9,2,0)+type LocatedAn a = GHC.LocatedAn a+#else+type LocatedAn a = GHC.Located+#endif++#if MIN_VERSION_ghc(9,2,0)+locA :: SrcSpanAnn' a -> SrcSpan+locA = GHC.locA+#else+locA = id+#endif++#if MIN_VERSION_ghc(9,2,0)+getLocA :: SrcLoc.GenLocated (SrcSpanAnn' a) e -> SrcSpan+getLocA = GHC.getLocA+#else+-- getLocA :: HasSrcSpan a => a -> SrcSpan+getLocA x = GHC.getLoc x+#endif++noLocA :: a -> LocatedAn an a+#if MIN_VERSION_ghc(9,2,0)+noLocA = GHC.noLocA+#else+noLocA = GHC.noLoc+#endif++#if !MIN_VERSION_ghc(9,2,0)+type AnnListItem = SrcLoc.SrcSpan+#endif++#if !MIN_VERSION_ghc(9,2,0)+type NameAnn = SrcLoc.SrcSpan+#endif++pattern GRE :: Name -> Parent -> Bool -> [ImportSpec] -> RdrName.GlobalRdrElt+{-# COMPLETE GRE #-}+#if MIN_VERSION_ghc(9,2,0)+pattern GRE{gre_name, gre_par, gre_lcl, gre_imp} <- RdrName.GRE+ {gre_name = (greNamePrintableName -> gre_name)+ ,gre_par, gre_lcl, gre_imp}+#else+pattern GRE{gre_name, gre_par, gre_lcl, gre_imp} = RdrName.GRE{..}+#endif++#if MIN_VERSION_ghc(9,2,0)+collectHsBindsBinders :: CollectPass p => Bag (XRec p (HsBindLR p idR)) -> [IdP p]+collectHsBindsBinders x = GHC.collectHsBindsBinders CollNoDictBinders x+#endif++#if !MIN_VERSION_ghc(9,2,0)+pattern HsLet xlet localBinds expr <- GHC.HsLet xlet (SrcLoc.unLoc -> localBinds) expr+pattern LetStmt xlet localBinds <- GHC.LetStmt xlet (SrcLoc.unLoc -> localBinds) #endif
src/Development/IDE/GHC/Compat/Env.hs view
@@ -32,6 +32,7 @@ -- * DynFlags Helper setBytecodeLinkerOptions, setInterpreterLinkerOptions,+ Development.IDE.GHC.Compat.Env.safeImportsOn, -- * Ways Ways, Way,@@ -80,8 +81,10 @@ #endif #if MIN_VERSION_ghc(9,0,0)+#if !MIN_VERSION_ghc(9,2,0) import qualified Data.Set as Set #endif+#endif #if !MIN_VERSION_ghc(9,2,0) import Data.IORef #endif@@ -102,12 +105,7 @@ #endif hscSetFlags :: DynFlags -> HscEnv -> HscEnv-hscSetFlags df env =-#if MIN_VERSION_ghc(9,2,0)- hscSetFlags df env-#else- env { Env.hsc_dflags = df }-#endif+hscSetFlags df env = env { Env.hsc_dflags = df } initTempFs :: HscEnv -> IO HscEnv initTempFs env = do@@ -178,6 +176,13 @@ thisPackage #endif +safeImportsOn :: DynFlags -> Bool+safeImportsOn =+#if MIN_VERSION_ghc(9,2,0)+ Session.safeImportsOn+#else+ DynFlags.safeImportsOn+#endif #if MIN_VERSION_ghc(9,0,0) && !MIN_VERSION_ghc(9,2,0) type HomeUnit = Unit
+ src/Development/IDE/GHC/Compat/ExactPrint.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE PatternSynonyms #-}++-- | This module contains compatibility constructs to write type signatures across+-- multiple ghc-exactprint versions, accepting that anything more ambitious is+-- pretty much impossible with the GHC 9.2 redesign of ghc-exactprint+module Development.IDE.GHC.Compat.ExactPrint+ ( ExactPrint+ , exactPrint+ , makeDeltaAst+ , Retrie.Annotated, pattern Annotated, astA, annsA+ ) where++#if !MIN_VERSION_ghc(9,2,0)+import Control.Arrow ((&&&))+#else+import Development.IDE.GHC.Compat.Parser+#endif+import Language.Haskell.GHC.ExactPrint as Retrie+import qualified Retrie.ExactPrint as Retrie++#if !MIN_VERSION_ghc(9,2,0)+class ExactPrint ast where+ makeDeltaAst :: ast -> ast+ makeDeltaAst = id++instance ExactPrint ast+#endif++#if !MIN_VERSION_ghc(9,2,0)+pattern Annotated :: ast -> Anns -> Retrie.Annotated ast+pattern Annotated {astA, annsA} <- (Retrie.astA &&& Retrie.annsA -> (astA, annsA))+#else+pattern Annotated :: ast -> ApiAnns -> Retrie.Annotated ast+pattern Annotated {astA, annsA} <- ((,()) . Retrie.astA -> (astA, annsA))+#endif
src/Development/IDE/GHC/Compat/Outputable.hs view
@@ -31,17 +31,18 @@ #if MIN_VERSION_ghc(9,2,0)+import GHC.Driver.Env import GHC.Driver.Ppr import GHC.Driver.Session import GHC.Parser.Errors import qualified GHC.Parser.Errors.Ppr as Ppr import qualified GHC.Types.Error as Error import GHC.Types.Name.Ppr+import GHC.Types.Name.Reader import GHC.Types.SourceError import GHC.Types.SrcLoc import GHC.Unit.State import GHC.Utils.Error hiding (mkWarnMsg)-import GHC.Utils.Logger import GHC.Utils.Outputable import GHC.Utils.Panic #elif MIN_VERSION_ghc(9,0,0)@@ -136,7 +137,15 @@ formatErrorWithQual :: DynFlags -> MsgEnvelope DecoratedSDoc -> String formatErrorWithQual dflags e = #if MIN_VERSION_ghc(9,2,0)- showSDoc dflags (pprLocMsgEnvelope e)+ showSDoc dflags (pprNoLocMsgEnvelope e)++pprNoLocMsgEnvelope :: Error.RenderableDiagnostic e => MsgEnvelope e -> SDoc+pprNoLocMsgEnvelope (MsgEnvelope { errMsgDiagnostic = e+ , errMsgContext = unqual })+ = sdocWithContext $ \ctx ->+ withErrStyle unqual $+ (formatBulleted ctx $ Error.renderDiagnostic e)+ #else Out.showSDoc dflags $ Out.withPprStyle (oldMkErrStyle dflags $ errMsgContext e)@@ -152,9 +161,15 @@ type PsError = ErrMsg #endif -mkPrintUnqualifiedDefault :: GlobalRdrEnv -> PrintUnqualified-mkPrintUnqualifiedDefault =- HscTypes.mkPrintUnqualified unsafeGlobalDynFlags+mkPrintUnqualifiedDefault :: HscEnv -> GlobalRdrEnv -> PrintUnqualified+mkPrintUnqualifiedDefault env =+#if MIN_VERSION_ghc(9,2,0)+ -- GHC 9.2.1 version+ -- mkPrintUnqualified :: UnitEnv -> GlobalRdrEnv -> PrintUnqualified+ mkPrintUnqualified (hsc_unit_env env)+#else+ HscTypes.mkPrintUnqualified (hsc_dflags env)+#endif mkWarnMsg :: DynFlags -> SrcSpan -> PrintUnqualified -> SDoc -> MsgEnvelope DecoratedSDoc mkWarnMsg =
src/Development/IDE/GHC/Compat/Parser.hs view
@@ -1,4 +1,6 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE PatternSynonyms #-}+{-# HLINT ignore "Unused LANGUAGE pragma" #-} -- | Parser compaibility module. module Development.IDE.GHC.Compat.Parser (@@ -14,24 +16,52 @@ #else ApiAnns, #endif- mkHsParsedModule,- mkParsedModule,+#if MIN_VERSION_ghc(9,0,0)+ PsSpan(..),+#endif+#if MIN_VERSION_ghc(9,2,0)+ pattern HsParsedModule,+ type GHC.HsParsedModule,+ Development.IDE.GHC.Compat.Parser.hpm_module,+ Development.IDE.GHC.Compat.Parser.hpm_src_files,+ Development.IDE.GHC.Compat.Parser.hpm_annotations,+ pattern ParsedModule,+ Development.IDE.GHC.Compat.Parser.pm_parsed_source,+ type GHC.ParsedModule,+ Development.IDE.GHC.Compat.Parser.pm_mod_summary,+ Development.IDE.GHC.Compat.Parser.pm_extra_src_files,+ Development.IDE.GHC.Compat.Parser.pm_annotations,+#else+ GHC.HsParsedModule(..),+ GHC.ParsedModule(..),+#endif mkApiAnns, -- * API Annotations Anno.AnnKeywordId(..),+#if !MIN_VERSION_ghc(9,2,0) Anno.AnnotationComment(..),+#endif ) where #if MIN_VERSION_ghc(9,0,0)+#if !MIN_VERSION_ghc(9,2,0)+import qualified GHC.Driver.Types as GHC+#endif+import qualified GHC.Parser.Annotation as Anno import qualified GHC.Parser.Lexer as Lexer+import GHC.Types.SrcLoc (PsSpan (..)) #if MIN_VERSION_ghc(9,2,0)+import GHC (pm_extra_src_files,+ pm_mod_summary,+ pm_parsed_source)+import qualified GHC import qualified GHC.Driver.Config as Config+import GHC.Hs (hpm_module, hpm_src_files) import GHC.Parser.Lexer hiding (initParserState)-#else-import qualified GHC.Parser.Annotation as Anno #endif #else import qualified ApiAnnotation as Anno+import qualified HscTypes as GHC import Lexer import qualified SrcLoc #endif@@ -40,6 +70,7 @@ #if !MIN_VERSION_ghc(9,2,0) import qualified Data.Map as Map+import qualified GHC #endif #if !MIN_VERSION_ghc(9,0,0)@@ -74,27 +105,36 @@ type ApiAnns = Anno.ApiAnns #endif --mkHsParsedModule :: ParsedSource -> [FilePath] -> ApiAnns -> HsParsedModule-mkHsParsedModule parsed fps hpm_annotations =- HsParsedModule- parsed- fps-#if !MIN_VERSION_ghc(9,2,0)- hpm_annotations+#if MIN_VERSION_ghc(9,2,0)+pattern HsParsedModule :: Located HsModule -> [FilePath] -> ApiAnns -> GHC.HsParsedModule+pattern HsParsedModule+ { hpm_module+ , hpm_src_files+ , hpm_annotations+ } <- ( (,()) -> (GHC.HsParsedModule{..}, hpm_annotations))+ where+ HsParsedModule hpm_module hpm_src_files hpm_annotations =+ GHC.HsParsedModule hpm_module hpm_src_files #endif -mkParsedModule :: ModSummary -> ParsedSource -> [FilePath] -> ApiAnns -> ParsedModule-mkParsedModule ms parsed extra_src_files _hpm_annotations =- ParsedModule {- pm_mod_summary = ms- , pm_parsed_source = parsed- , pm_extra_src_files = extra_src_files-#if !MIN_VERSION_ghc(9,2,0)- , pm_annotations = _hpm_annotations+#if MIN_VERSION_ghc(9,2,0)+pattern ParsedModule :: ModSummary -> ParsedSource -> [FilePath] -> ApiAnns -> GHC.ParsedModule+pattern ParsedModule+ { pm_mod_summary+ , pm_parsed_source+ , pm_extra_src_files+ , pm_annotations+ } <- ( (,()) -> (GHC.ParsedModule{..}, pm_annotations))+ where+ ParsedModule ms parsed extra_src_files _anns =+ GHC.ParsedModule+ { pm_mod_summary = ms+ , pm_parsed_source = parsed+ , pm_extra_src_files = extra_src_files+ }+{-# COMPLETE ParsedModule :: GHC.ParsedModule #-} #endif- } mkApiAnns :: PState -> ApiAnns #if MIN_VERSION_ghc(9,2,0)
src/Development/IDE/GHC/Compat/Plugins.hs view
@@ -17,7 +17,6 @@ #endif ) where -import GHC #if MIN_VERSION_ghc(9,0,0) #if MIN_VERSION_ghc(9,2,0) import qualified GHC.Driver.Env as Env@@ -51,7 +50,7 @@ dflags #endif applyPluginAction- (mkHsParsedModule parsed [] hpm_annotations)+ (HsParsedModule parsed [] hpm_annotations) initializePlugins :: HscEnv -> IO HscEnv initializePlugins env = do
src/Development/IDE/GHC/Compat/Units.hs view
@@ -46,12 +46,14 @@ -- * Utils filterInplaceUnits, FinderCache,+ showSDocForUser', ) where #if MIN_VERSION_ghc(9,0,0) #if MIN_VERSION_ghc(9,2,0) import qualified GHC.Data.ShortText as ST import GHC.Driver.Env (hsc_unit_dbs)+import GHC.Driver.Ppr import GHC.Unit.Env import GHC.Unit.External import GHC.Unit.Finder@@ -67,9 +69,11 @@ import qualified GHC.Unit.State as State import GHC.Unit.Types hiding (moduleUnit, toUnitId) import qualified GHC.Unit.Types as Unit+import GHC.Utils.Outputable #else import qualified DynFlags import FastString+import GhcPlugins (SDoc, showSDocForUser) import HscTypes import Module hiding (moduleUnitId) import qualified Module@@ -89,6 +93,7 @@ #endif import Data.Either import Data.Version+import qualified GHC #if MIN_VERSION_ghc(9,0,0) type PreloadUnitClosure = UniqSet UnitId@@ -131,12 +136,12 @@ let cached_unit_dbs = hsc_unit_dbs env (dbs,unit_state,home_unit,mconstants) <- State.initUnits (hsc_logger env) dflags1 cached_unit_dbs - dflags <- updatePlatformConstants dflags1 mconstants+ dflags <- DynFlags.updatePlatformConstants dflags1 mconstants let unit_env = UnitEnv { ue_platform = targetPlatform dflags- , ue_namever = ghcNameVersion dflags+ , ue_namever = DynFlags.ghcNameVersion dflags , ue_home_unit = home_unit , ue_units = unit_state }@@ -274,8 +279,11 @@ -- ------------------------------------------------------------------ #if MIN_VERSION_ghc(9,2,0)+definiteUnitId :: Definite uid -> GenUnit uid definiteUnitId = RealUnit+defUnitId :: unit -> Definite unit defUnitId = Definite+installedModule :: unit -> ModuleName -> GenModule unit installedModule = Module #elif MIN_VERSION_ghc(9,0,0)@@ -340,3 +348,10 @@ else Right p #endif isInplace p = Right p++showSDocForUser' :: HscEnv -> GHC.PrintUnqualified -> SDoc -> String+#if MIN_VERSION_ghc(9,2,0)+showSDocForUser' env = showSDocForUser (hsc_dflags env) (unitState env)+#else+showSDocForUser' env = showSDocForUser (hsc_dflags env)+#endif
src/Development/IDE/GHC/Compat/Util.hs view
@@ -67,6 +67,10 @@ StringBuffer(..), hGetStringBuffer, stringToStringBuffer,+ nextChar,+ atEnd,+ -- * Char+ is_ident ) where #if MIN_VERSION_ghc(9,0,0)@@ -79,6 +83,7 @@ import GHC.Data.Maybe import GHC.Data.Pair import GHC.Data.StringBuffer+import GHC.Parser.CharClass (is_ident) import GHC.Types.Unique import GHC.Types.Unique.DFM import GHC.Utils.Fingerprint@@ -88,6 +93,7 @@ #else import Bag import BooleanFormula+import Ctype (is_ident) import EnumSet import qualified Exception import FastString
+ src/Development/IDE/GHC/Dump.hs view
@@ -0,0 +1,337 @@+{-# LANGUAGE CPP #-}+module Development.IDE.GHC.Dump(showAstDataHtml) where+import Data.Data hiding (Fixity)+import Development.IDE.GHC.Compat hiding (NameAnn)+#if MIN_VERSION_ghc(8,10,1)+import GHC.Hs.Dump+#else+import HsDumpAst+#endif+#if MIN_VERSION_ghc(9,2,1)+import qualified Data.ByteString as B+import Development.IDE.GHC.Compat.Util+import GHC.Hs+import Generics.SYB (ext1Q, ext2Q, extQ)+#endif+#if MIN_VERSION_ghc(9,0,1)+import GHC.Plugins+#else+import GhcPlugins+#endif+import Prelude hiding ((<>))++-- | Show a GHC syntax tree in HTML.+#if MIN_VERSION_ghc(9,2,1)+showAstDataHtml :: (Data a, ExactPrint a, Outputable a) => a -> SDoc+#else+showAstDataHtml :: (Data a, Outputable a) => a -> SDoc+#endif+showAstDataHtml a0 = html $+ header $$+ body (tag' [("id",text (show @String "myUL"))] "ul" $ vcat+ [+#if MIN_VERSION_ghc(9,2,1)+ li (pre $ text (exactPrint a0)),+ li (showAstDataHtml' a0),+ li (nested "Raw" $ pre $ showAstData NoBlankSrcSpan NoBlankEpAnnotations a0)+#else+ li (nested "Raw" $ pre $ showAstData NoBlankSrcSpan a0)+#endif+ ])+ where+ tag = tag' []+ tag' attrs t cont =+ angleBrackets (text t <+> hcat [text a<>char '=' <>v | (a,v) <- attrs])+ <> cont+ <> angleBrackets (char '/' <> text t)+ ul = tag' [("class", text (show @String "nested"))] "ul"+ li = tag "li"+ caret x = tag' [("class", text "caret")] "span" "" <+> x+ nested foo cts+#if MIN_VERSION_ghc(9,2,1)+ | cts == empty = foo+#endif+ | otherwise = foo $$ (caret $ ul cts)+ body cts = tag "body" $ cts $$ tag "script" (text js)+ header = tag "head" $ tag "style" $ text css+ html = tag "html"+ pre = tag "pre"+#if MIN_VERSION_ghc(9,2,1)+ showAstDataHtml' :: Data a => a -> SDoc+ showAstDataHtml' =+ (generic+ `ext1Q` list+ `extQ` string `extQ` fastString `extQ` srcSpan `extQ` realSrcSpan+ `extQ` annotation+ `extQ` annotationModule+ `extQ` annotationAddEpAnn+ `extQ` annotationGrhsAnn+ `extQ` annotationEpAnnHsCase+ `extQ` annotationEpAnnHsLet+ `extQ` annotationAnnList+ `extQ` annotationEpAnnImportDecl+ `extQ` annotationAnnParen+ `extQ` annotationTrailingAnn+ `extQ` annotationEpaLocation+ `extQ` addEpAnn+ `extQ` lit `extQ` litr `extQ` litt+ `extQ` sourceText+ `extQ` deltaPos+ `extQ` epaAnchor+ `extQ` anchorOp+ `extQ` bytestring+ `extQ` name `extQ` occName `extQ` moduleName `extQ` var+ `extQ` dataCon+ `extQ` bagName `extQ` bagRdrName `extQ` bagVar `extQ` nameSet+ `extQ` fixity+ `ext2Q` located+ `extQ` srcSpanAnnA+ `extQ` srcSpanAnnL+ `extQ` srcSpanAnnP+ `extQ` srcSpanAnnC+ `extQ` srcSpanAnnN+ )++ where generic :: Data a => a -> SDoc+ generic t = nested (text $ showConstr (toConstr t))+ (vcat (gmapQ (li . showAstDataHtml') t))++ string :: String -> SDoc+ string = text . normalize_newlines . show++ fastString :: FastString -> SDoc+ fastString s = braces $+ text "FastString:"+ <+> text (normalize_newlines . show $ s)++ bytestring :: B.ByteString -> SDoc+ bytestring = text . normalize_newlines . show++ list [] = brackets empty+ list [x] = "[]" $$ showAstDataHtml' x+ list xs = nested "[]" (vcat $ map (li . showAstDataHtml') xs)++ -- Eliminate word-size dependence+ lit :: HsLit GhcPs -> SDoc+ lit (HsWordPrim s x) = numericLit "HsWord{64}Prim" x s+ lit (HsWord64Prim s x) = numericLit "HsWord{64}Prim" x s+ lit (HsIntPrim s x) = numericLit "HsInt{64}Prim" x s+ lit (HsInt64Prim s x) = numericLit "HsInt{64}Prim" x s+ lit l = generic l++ litr :: HsLit GhcRn -> SDoc+ litr (HsWordPrim s x) = numericLit "HsWord{64}Prim" x s+ litr (HsWord64Prim s x) = numericLit "HsWord{64}Prim" x s+ litr (HsIntPrim s x) = numericLit "HsInt{64}Prim" x s+ litr (HsInt64Prim s x) = numericLit "HsInt{64}Prim" x s+ litr l = generic l++ litt :: HsLit GhcTc -> SDoc+ litt (HsWordPrim s x) = numericLit "HsWord{64}Prim" x s+ litt (HsWord64Prim s x) = numericLit "HsWord{64}Prim" x s+ litt (HsIntPrim s x) = numericLit "HsInt{64}Prim" x s+ litt (HsInt64Prim s x) = numericLit "HsInt{64}Prim" x s+ litt l = generic l++ numericLit :: String -> Integer -> SourceText -> SDoc+ numericLit tag x s = braces $ hsep [ text tag+ , generic x+ , generic s ]++ sourceText :: SourceText -> SDoc+ sourceText NoSourceText = text "NoSourceText"+ sourceText (SourceText src) = text "SourceText" <+> text src++ epaAnchor :: EpaLocation -> SDoc+ epaAnchor (EpaSpan r) = text "EpaSpan" <+> realSrcSpan r+ epaAnchor (EpaDelta d cs) = text "EpaDelta" <+> deltaPos d <+> showAstDataHtml' cs++ anchorOp :: AnchorOperation -> SDoc+ anchorOp UnchangedAnchor = "UnchangedAnchor"+ anchorOp (MovedAnchor dp) = "MovedAnchor " <> deltaPos dp++ deltaPos :: DeltaPos -> SDoc+ deltaPos (SameLine c) = text "SameLine" <+> ppr c+ deltaPos (DifferentLine l c) = text "DifferentLine" <+> ppr l <+> ppr c++ name :: Name -> SDoc+ name nm = braces $ text "Name:" <+> ppr nm++ occName n = braces $+ text "OccName:"+ <+> text (occNameString n)++ moduleName :: ModuleName -> SDoc+ moduleName m = braces $ text "ModuleName:" <+> ppr m++ srcSpan :: SrcSpan -> SDoc+ srcSpan ss = char ' ' <>+ (hang (ppr ss) 1+ -- TODO: show annotations here+ (text ""))++ realSrcSpan :: RealSrcSpan -> SDoc+ realSrcSpan ss = braces $ char ' ' <>+ (hang (ppr ss) 1+ -- TODO: show annotations here+ (text ""))++ addEpAnn :: AddEpAnn -> SDoc+ addEpAnn (AddEpAnn a s) = text "AddEpAnn" <+> ppr a <+> epaAnchor s++ var :: Var -> SDoc+ var v = braces $ text "Var:" <+> ppr v++ dataCon :: DataCon -> SDoc+ dataCon c = braces $ text "DataCon:" <+> ppr c++ bagRdrName:: Bag (LocatedA (HsBind GhcPs)) -> SDoc+ bagRdrName bg = braces $+ text "Bag(LocatedA (HsBind GhcPs)):"+ $$ (list . bagToList $ bg)++ bagName :: Bag (LocatedA (HsBind GhcRn)) -> SDoc+ bagName bg = braces $+ text "Bag(LocatedA (HsBind Name)):"+ $$ (list . bagToList $ bg)++ bagVar :: Bag (LocatedA (HsBind GhcTc)) -> SDoc+ bagVar bg = braces $+ text "Bag(LocatedA (HsBind Var)):"+ $$ (list . bagToList $ bg)++ nameSet ns = braces $+ text "NameSet:"+ $$ (list . nameSetElemsStable $ ns)++ fixity :: Fixity -> SDoc+ fixity fx = braces $+ text "Fixity:"+ <+> ppr fx++ located :: (Data a, Data b) => GenLocated a b -> SDoc+ located (L ss a)+ = nested "L" $ (li (showAstDataHtml' ss) $$ li (showAstDataHtml' a))++ -- -------------------------++ annotation :: EpAnn [AddEpAnn] -> SDoc+ annotation = annotation' (text "EpAnn [AddEpAnn]")++ annotationModule :: EpAnn AnnsModule -> SDoc+ annotationModule = annotation' (text "EpAnn AnnsModule")++ annotationAddEpAnn :: EpAnn AddEpAnn -> SDoc+ annotationAddEpAnn = annotation' (text "EpAnn AddEpAnn")++ annotationGrhsAnn :: EpAnn GrhsAnn -> SDoc+ annotationGrhsAnn = annotation' (text "EpAnn GrhsAnn")++ annotationEpAnnHsCase :: EpAnn EpAnnHsCase -> SDoc+ annotationEpAnnHsCase = annotation' (text "EpAnn EpAnnHsCase")++ annotationEpAnnHsLet :: EpAnn AnnsLet -> SDoc+ annotationEpAnnHsLet = annotation' (text "EpAnn AnnsLet")++ annotationAnnList :: EpAnn AnnList -> SDoc+ annotationAnnList = annotation' (text "EpAnn AnnList")++ annotationEpAnnImportDecl :: EpAnn EpAnnImportDecl -> SDoc+ annotationEpAnnImportDecl = annotation' (text "EpAnn EpAnnImportDecl")++ annotationAnnParen :: EpAnn AnnParen -> SDoc+ annotationAnnParen = annotation' (text "EpAnn AnnParen")++ annotationTrailingAnn :: EpAnn TrailingAnn -> SDoc+ annotationTrailingAnn = annotation' (text "EpAnn TrailingAnn")++ annotationEpaLocation :: EpAnn EpaLocation -> SDoc+ annotationEpaLocation = annotation' (text "EpAnn EpaLocation")++ annotation' :: forall a .(Data a, Typeable a)+ => SDoc -> EpAnn a -> SDoc+ annotation' tag anns = nested (text $ showConstr (toConstr anns))+ (vcat (map li $ gmapQ showAstDataHtml' anns))++ -- -------------------------++ srcSpanAnnA :: SrcSpanAnn' (EpAnn AnnListItem) -> SDoc+ srcSpanAnnA = locatedAnn'' (text "SrcSpanAnnA")++ srcSpanAnnL :: SrcSpanAnn' (EpAnn AnnList) -> SDoc+ srcSpanAnnL = locatedAnn'' (text "SrcSpanAnnL")++ srcSpanAnnP :: SrcSpanAnn' (EpAnn AnnPragma) -> SDoc+ srcSpanAnnP = locatedAnn'' (text "SrcSpanAnnP")++ srcSpanAnnC :: SrcSpanAnn' (EpAnn AnnContext) -> SDoc+ srcSpanAnnC = locatedAnn'' (text "SrcSpanAnnC")++ srcSpanAnnN :: SrcSpanAnn' (EpAnn NameAnn) -> SDoc+ srcSpanAnnN = locatedAnn'' (text "SrcSpanAnnN")++ locatedAnn'' :: forall a. (Typeable a, Data a)+ => SDoc -> SrcSpanAnn' a -> SDoc+ locatedAnn'' tag ss =+ case cast ss of+ Just ((SrcSpanAnn ann s) :: SrcSpanAnn' a) ->+ nested "SrcSpanAnn" $ (+ li(showAstDataHtml' ann)+ $$ li(srcSpan s))+ Nothing -> text "locatedAnn:unmatched" <+> tag+ <+> (text (showConstr (toConstr ss)))+#endif+++normalize_newlines :: String -> String+normalize_newlines ('\\':'r':'\\':'n':xs) = '\\':'n':normalize_newlines xs+normalize_newlines (x:xs) = x:normalize_newlines xs+normalize_newlines [] = []++css :: String+css = unlines+ [ "body {background-color: black; color: white ;}"+ , "/* Remove default bullets */"+ , "ul, #myUL {"+ , " list-style-type: none;"+ , "}"+ , "/* Remove margins and padding from the parent ul */"+ , "#myUL {"+ , " margin: 0; "+ , " padding: 0; "+ , "} "+ , "/* Style the caret/arrow */ "+ , ".caret { "+ , " cursor: pointer; "+ , " user-select: none; /* Prevent text selection */"+ , "} "+ , "/* Create the caret/arrow with a unicode, and style it */"+ , ".caret::before { "+ , " content: \"\\25B6 \"; "+ , " color: white; "+ , " display: inline-block; "+ , " margin-right: 6px; "+ , "} "+ , "/* Rotate the caret/arrow icon when clicked on (using JavaScript) */"+ , ".caret-down::before { "+ , " transform: rotate(90deg); "+ , "} "+ , "/* Hide the nested list */ "+ , ".nested { "+ , " display: none; "+ , "} "+ , "/* Show the nested list when the user clicks on the caret/arrow (with JavaScript) */"+ , ".active { "+ , " display: block;}"+ ]++js :: String+js = unlines+ [ "var toggler = document.getElementsByClassName(\"caret\");"+ , "var i;"+ , "for (i = 0; i < toggler.length; i++) {"+ , " toggler[i].addEventListener(\"click\", function() {"+ , " this.parentElement.querySelector(\".nested\").classList.toggle(\"active\");"+ , " this.classList.toggle(\"caret-down\");"+ , " }); }"+ ]
src/Development/IDE/GHC/Error.hs view
@@ -79,7 +79,7 @@ realSrcLocToPosition :: RealSrcLoc -> Position realSrcLocToPosition real =- Position (srcLocLine real - 1) (srcLocCol real - 1)+ Position (fromIntegral $ srcLocLine real - 1) (fromIntegral $ srcLocCol real - 1) -- | Extract a file name from a GHC SrcSpan (use message for unhelpful ones) -- FIXME This may not be an _absolute_ file name, needs fixing.@@ -111,7 +111,7 @@ positionToRealSrcLoc :: NormalizedFilePath -> Position -> RealSrcLoc positionToRealSrcLoc nfp (Position l c)=- Compat.mkRealSrcLoc (fromString $ fromNormalizedFilePath nfp) (l + 1) (c + 1)+ Compat.mkRealSrcLoc (fromString $ fromNormalizedFilePath nfp) (fromIntegral $ l + 1) (fromIntegral $ c + 1) isInsideSrcSpan :: Position -> SrcSpan -> Bool p `isInsideSrcSpan` r = case srcSpanToRange r of
src/Development/IDE/GHC/ExactPrint.hs view
@@ -1,9 +1,12 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DerivingVia #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-} +-- | This module hosts various abstractions and utility functions to work with ghc-exactprint. module Development.IDE.GHC.ExactPrint ( Graft(..), graftDecls,@@ -18,30 +21,41 @@ graftSmallestDeclsWithM, transform, transformM,+ ExactPrint(..),+#if !MIN_VERSION_ghc(9,2,0) useAnnotatedSource,+ Anns,+ Annotate,+ setPrecedingLinesT,+#else+ addParens,+ addParensToCtxt,+ modifyAnns,+ removeComma,+ -- * Helper function+ eqSrcSpan,+ epl,+ epAnn,+ removeTrailingComma,+#endif annotateParsedSource, getAnnotatedParsedSourceRule, GetAnnotatedParsedSource(..), ASTElement (..), ExceptStringT (..),- Annotated(..), TransformT,- Anns,- Annotate,- setPrecedingLinesT,- -- * Helper function- eqSrcSpan, ) where import Control.Applicative (Alternative)-import Control.Arrow+import Control.Arrow (right, (***)) import Control.Monad import qualified Control.Monad.Fail as Fail import Control.Monad.IO.Class (MonadIO) import Control.Monad.Trans.Class import Control.Monad.Trans.Except import Control.Monad.Zip+import Data.Bifunctor import Data.Bool (bool) import qualified Data.DList as DL import Data.Either.Extra (mapLeft)@@ -64,15 +78,26 @@ import Generics.SYB import Generics.SYB.GHC import Ide.PluginUtils-import Language.Haskell.GHC.ExactPrint import Language.Haskell.GHC.ExactPrint.Parsers import Language.LSP.Types import Language.LSP.Types.Capabilities (ClientCapabilities)-import Retrie.ExactPrint hiding (parseDecl,- parseExpr,+import Retrie.ExactPrint hiding (Annotated (..),+ parseDecl, parseExpr, parsePattern, parseType)-+#if MIN_VERSION_ghc(9,2,0)+import GHC (EpAnn (..),+ NameAdornment (NameParens),+ NameAnn (..),+ SrcSpanAnn' (SrcSpanAnn),+ SrcSpanAnnA,+ TrailingAnn (AddCommaAnn),+ emptyComments,+ spanAsAnchor)+import GHC.Parser.Annotation (AnnContext (..),+ DeltaPos (SameLine),+ EpaLocation (EpaDelta))+#endif ------------------------------------------------------------------------------ @@ -89,9 +114,15 @@ pm <- use GetParsedModuleWithComments nfp return ([], fmap annotateParsedSource pm) +#if MIN_VERSION_ghc(9,2,0) annotateParsedSource :: ParsedModule -> Annotated ParsedSource+annotateParsedSource (ParsedModule _ ps _ _) = unsafeMkA (makeDeltaAst ps) 0+#else+annotateParsedSource :: ParsedModule -> Annotated ParsedSource annotateParsedSource = fixAnns+#endif +#if !MIN_VERSION_ghc(9,2,0) useAnnotatedSource :: String -> IdeState ->@@ -99,6 +130,8 @@ IO (Maybe (Annotated ParsedSource)) useAnnotatedSource herald state nfp = runAction herald state (use GetAnnotatedParsedSource nfp)+#endif+ ------------------------------------------------------------------------------ {- | A transformation for grafting source trees together. Use the semigroup@@ -214,8 +247,8 @@ ast@, or this is a no-op. -} graft' ::- forall ast a.- (Data a, ASTElement ast) =>+ forall ast a l.+ (Data a, Typeable l, ASTElement l ast) => -- | Do we need to insert a space before this grafting? In do blocks, the -- answer is no, or we will break layout. But in function applications, -- the answer is yes, or the function call won't get its argument. Yikes!@@ -223,21 +256,26 @@ -- More often the answer is yes, so when in doubt, use that. Bool -> SrcSpan ->- Located ast ->+ LocatedAn l ast -> Graft (Either String) a graft' needs_space dst val = Graft $ \dflags a -> do+#if MIN_VERSION_ghc(9,2,0)+ val' <- annotate dflags needs_space val+#else (anns, val') <- annotate dflags needs_space val modifyAnnsT $ mappend anns+#endif pure $ everywhere' ( mkT $ \case- (L src _ :: Located ast)- | src `eqSrcSpan` dst -> val'+ (L src _ :: LocatedAn l ast)+ | locA src `eqSrcSpan` dst -> val' l -> l ) a + -- | Like 'graft', but specialized to 'LHsExpr', and intelligently inserts -- parentheses if they're necessary. graftExpr ::@@ -254,12 +292,11 @@ dflags a - getNeedsSpaceAndParenthesize ::- (ASTElement ast, Data a) =>+ (ASTElement l ast, Data a) => SrcSpan -> a ->- (Bool, Located ast -> Located ast)+ (Bool, LocatedAn l ast -> LocatedAn l ast) getNeedsSpaceAndParenthesize dst a = -- Traverse the tree, looking for our replacement node. But keep track of -- the context (parent HsExpr constructor) we're in while we do it. This@@ -267,7 +304,7 @@ let (needs_parens, needs_space) = everythingWithContext (Nothing, Nothing) (<>) ( mkQ (mempty, ) $ \x s -> case x of- (L src _ :: LHsExpr GhcPs) | src `eqSrcSpan` dst ->+ (L src _ :: LHsExpr GhcPs) | locA src `eqSrcSpan` dst -> (s, s) L _ x' -> (mempty, Just *** Just $ needsParensSpace x') ) a@@ -291,40 +328,54 @@ ( mkM $ \case val@(L src _ :: LHsExpr GhcPs)- | src `eqSrcSpan` dst -> do+ | locA src `eqSrcSpan` dst -> do mval <- trans val case mval of Just val' -> do+#if MIN_VERSION_ghc(9,2,0)+ val'' <-+ hoistTransform (either Fail.fail pure)+ (annotate @AnnListItem @(HsExpr GhcPs) dflags needs_space (mk_parens val'))+ pure val''+#else (anns, val'') <- hoistTransform (either Fail.fail pure)- (annotate @(HsExpr GhcPs) dflags needs_space (mk_parens val'))+ (annotate @AnnListItem @(HsExpr GhcPs) dflags needs_space (mk_parens val')) modifyAnnsT $ mappend anns pure val''+#endif Nothing -> pure val l -> pure l ) a graftWithM ::- forall ast m a.- (Fail.MonadFail m, Data a, ASTElement ast) =>+ forall ast m a l.+ (Fail.MonadFail m, Data a, Typeable l, ASTElement l ast) => SrcSpan ->- (Located ast -> TransformT m (Maybe (Located ast))) ->+ (LocatedAn l ast -> TransformT m (Maybe (LocatedAn l ast))) -> Graft m a graftWithM dst trans = Graft $ \dflags a -> do everywhereM' ( mkM $ \case- val@(L src _ :: Located ast)- | src `eqSrcSpan` dst -> do+ val@(L src _ :: LocatedAn l ast)+ | locA src `eqSrcSpan` dst -> do mval <- trans val case mval of Just val' -> do+#if MIN_VERSION_ghc(9,2,0)+ val'' <-+ hoistTransform (either Fail.fail pure) $+ annotate dflags True $ maybeParensAST val'+ pure val''+#else (anns, val'') <- hoistTransform (either Fail.fail pure) $ annotate dflags True $ maybeParensAST val' modifyAnnsT $ mappend anns pure val''+#endif Nothing -> pure val l -> pure l )@@ -368,7 +419,7 @@ annotateDecl dflags decl let go [] = DL.empty go (L src e : rest)- | src `eqSrcSpan` dst = DL.fromList decs <> DL.fromList rest+ | locA src `eqSrcSpan` dst = DL.fromList decs <> DL.fromList rest | otherwise = DL.singleton (L src e) <> go rest modifyDeclsT (pure . DL.toList . go) a @@ -381,7 +432,7 @@ graftSmallestDeclsWithM dst toDecls = Graft $ \dflags a -> do let go [] = pure DL.empty go (e@(L src _) : rest)- | dst `isSubspanOf` src = toDecls e >>= \case+ | dst `isSubspanOf` locA src = toDecls e >>= \case Just decs0 -> do decs <- forM decs0 $ \decl -> annotateDecl dflags decl@@ -399,7 +450,7 @@ graftDeclsWithM dst toDecls = Graft $ \dflags a -> do let go [] = pure DL.empty go (e@(L src _) : rest)- | src `eqSrcSpan` dst = toDecls e >>= \case+ | locA src `eqSrcSpan` dst = toDecls e >>= \case Just decs0 -> do decs <- forM decs0 $ \decl -> hoistTransform (either Fail.fail pure) $@@ -410,9 +461,9 @@ modifyDeclsT (fmap DL.toList . go) a -class (Data ast, Outputable ast) => ASTElement ast where- parseAST :: Parser (Located ast)- maybeParensAST :: Located ast -> Located ast+class (Data ast, Typeable l, Outputable l, Outputable ast) => ASTElement l ast | ast -> l where+ parseAST :: Parser (LocatedAn l ast)+ maybeParensAST :: LocatedAn l ast -> LocatedAn l ast {- | Construct a 'Graft', replacing the node at the given 'SrcSpan' with the given @Located ast@. The node at that position must already be a @Located ast@, or this is a no-op.@@ -421,16 +472,16 @@ forall a. (Data a) => SrcSpan ->- Located ast ->+ LocatedAn l ast -> Graft (Either String) a graft dst = graft' True dst . maybeParensAST -instance p ~ GhcPs => ASTElement (HsExpr p) where+instance p ~ GhcPs => ASTElement AnnListItem (HsExpr p) where parseAST = parseExpr maybeParensAST = parenthesize graft = graftExpr -instance p ~ GhcPs => ASTElement (Pat p) where+instance p ~ GhcPs => ASTElement AnnListItem (Pat p) where #if __GLASGOW_HASKELL__ == 808 parseAST = fmap (fmap $ right $ second dL) . parsePattern maybeParensAST = dL . parenthesizePat appPrec . unLoc@@ -439,41 +490,53 @@ maybeParensAST = parenthesizePat appPrec #endif -instance p ~ GhcPs => ASTElement (HsType p) where+instance p ~ GhcPs => ASTElement AnnListItem (HsType p) where parseAST = parseType maybeParensAST = parenthesizeHsType appPrec -instance p ~ GhcPs => ASTElement (HsDecl p) where+instance p ~ GhcPs => ASTElement AnnListItem (HsDecl p) where parseAST = parseDecl maybeParensAST = id -instance p ~ GhcPs => ASTElement (ImportDecl p) where+instance p ~ GhcPs => ASTElement AnnListItem (ImportDecl p) where parseAST = parseImport maybeParensAST = id -instance ASTElement RdrName where+instance ASTElement NameAnn RdrName where parseAST df fp = parseWith df fp parseIdentifier maybeParensAST = id ------------------------------------------------------------------------------ +#if !MIN_VERSION_ghc(9,2,0) -- | Dark magic I stole from retrie. No idea what it does. fixAnns :: ParsedModule -> Annotated ParsedSource fixAnns ParsedModule {..} = let ranns = relativiseApiAnns pm_parsed_source pm_annotations in unsafeMkA pm_parsed_source ranns 0+#endif ------------------------------------------------------------------------------ -- | Given an 'LHSExpr', compute its exactprint annotations. -- Note that this function will throw away any existing annotations (and format)-annotate :: ASTElement ast => DynFlags -> Bool -> Located ast -> TransformT (Either String) (Anns, Located ast)+annotate :: (ASTElement l ast, Outputable l)+#if MIN_VERSION_ghc(9,2,0)+ => DynFlags -> Bool -> LocatedAn l ast -> TransformT (Either String) (LocatedAn l ast)+#else+ => DynFlags -> Bool -> LocatedAn l ast -> TransformT (Either String) (Anns, LocatedAn l ast)+#endif annotate dflags needs_space ast = do uniq <- show <$> uniqueSrcSpanT let rendered = render dflags ast+#if MIN_VERSION_ghc(9,2,0)+ expr' <- lift $ mapLeft show $ parseAST dflags uniq rendered+ pure expr'+#else (anns, expr') <- lift $ mapLeft show $ parseAST dflags uniq rendered let anns' = setPrecedingLines expr' 0 (bool 0 1 needs_space) anns- pure (anns', expr')+ pure (anns',expr')+#endif -- | Given an 'LHsDecl', compute its exactprint annotations. annotateDecl :: DynFlags -> LHsDecl GhcPs -> TransformT (Either String) (LHsDecl GhcPs)@@ -489,6 +552,17 @@ let set_matches matches = ValD ext fb { fun_matches = mg { mg_alts = L alt_src matches }} +#if MIN_VERSION_ghc(9,2,0)+ alts' <- for alts $ \alt -> do+ uniq <- show <$> uniqueSrcSpanT+ let rendered = render dflags $ set_matches [alt]+ lift (mapLeft show $ parseDecl dflags uniq rendered) >>= \case+ (L _ (ValD _ FunBind { fun_matches = MG { mg_alts = L _ [alt']}}))+ -> pure alt'+ _ -> lift $ Left "annotateDecl: didn't parse a single FunBind match"++ pure $ L src $ set_matches alts'+#else (anns', alts') <- fmap unzip $ for alts $ \alt -> do uniq <- show <$> uniqueSrcSpanT let rendered = render dflags $ set_matches [alt]@@ -499,13 +573,18 @@ modifyAnnsT $ mappend $ fold anns' pure $ L src $ set_matches alts'+#endif annotateDecl dflags ast = do uniq <- show <$> uniqueSrcSpanT let rendered = render dflags ast+#if MIN_VERSION_ghc(9,2,0)+ lift $ mapLeft show $ parseDecl dflags uniq rendered+#else (anns, expr') <- lift $ mapLeft show $ parseDecl dflags uniq rendered let anns' = setPrecedingLines expr' 1 0 anns modifyAnnsT $ mappend anns' pure expr'+#endif ------------------------------------------------------------------------------ @@ -525,3 +604,60 @@ -- Ignores the (Maybe BufSpan) field of SrcSpan's. eqSrcSpan :: SrcSpan -> SrcSpan -> Bool eqSrcSpan l r = leftmost_smallest l r == EQ++-- | Equality on SrcSpan's.+-- Ignores the (Maybe BufSpan) field of SrcSpan's.+#if MIN_VERSION_ghc(9,2,0)+eqSrcSpanA :: SrcAnn la -> SrcAnn b -> Bool+eqSrcSpanA l r = leftmost_smallest (locA l) (locA r) == EQ+#else+eqSrcSpanA :: SrcSpan -> SrcSpan -> Bool+eqSrcSpanA l r = leftmost_smallest l r == EQ+#endif++#if MIN_VERSION_ghc(9,2,0)+addParensToCtxt :: Maybe EpaLocation -> AnnContext -> AnnContext+addParensToCtxt close_dp = addOpen . addClose+ where+ addOpen it@AnnContext{ac_open = []} = it{ac_open = [epl 0]}+ addOpen other = other+ addClose it+ | Just c <- close_dp = it{ac_close = [c]}+ | AnnContext{ac_close = []} <- it = it{ac_close = [epl 0]}+ | otherwise = it++epl :: Int -> EpaLocation+epl n = EpaDelta (SameLine n) []++epAnn :: SrcSpan -> ann -> EpAnn ann+epAnn srcSpan anns = EpAnn (spanAsAnchor srcSpan) anns emptyComments++modifyAnns :: LocatedAn a ast -> (a -> a) -> LocatedAn a ast+modifyAnns x f = first ((fmap.fmap) f) x++removeComma :: SrcSpanAnnA -> SrcSpanAnnA+removeComma it@(SrcSpanAnn EpAnnNotUsed _) = it+removeComma (SrcSpanAnn (EpAnn anc (AnnListItem as) cs) l)+ = (SrcSpanAnn (EpAnn anc (AnnListItem (filter (not . isCommaAnn) as)) cs) l)+ where+ isCommaAnn AddCommaAnn{} = True+ isCommaAnn _ = False++addParens :: Bool -> GHC.NameAnn -> GHC.NameAnn+addParens True it@NameAnn{} =+ it{nann_adornment = NameParens, nann_open = epl 0, nann_close = epl 0 }+addParens True it@NameAnnCommas{} =+ it{nann_adornment = NameParens, nann_open = epl 0, nann_close = epl 0 }+addParens True it@NameAnnOnly{} =+ it{nann_adornment = NameParens, nann_open = epl 0, nann_close = epl 0 }+addParens True NameAnnTrailing{..} =+ NameAnn{nann_adornment = NameParens, nann_open = epl 0, nann_close = epl 0, nann_name = epl 0, ..}+addParens _ it = it++removeTrailingComma :: GenLocated SrcSpanAnnA ast -> GenLocated SrcSpanAnnA ast+removeTrailingComma = flip modifyAnns $ \(AnnListItem l) -> AnnListItem $ filter (not . isCommaAnn) l++isCommaAnn :: TrailingAnn -> Bool+isCommaAnn AddCommaAnn{} = True+isCommaAnn _ = False+#endif
src/Development/IDE/GHC/Orphans.hs view
@@ -9,6 +9,9 @@ -- Note that the 'NFData' instances may not be law abiding. module Development.IDE.GHC.Orphans() where +#if MIN_VERSION_ghc(9,2,0)+import GHC.Parser.Annotation+#endif #if MIN_VERSION_ghc(9,0,0) import GHC.Data.Bag import GHC.Data.FastString@@ -25,7 +28,6 @@ import Unique (getKey) #endif -import GHC import Retrie.ExactPrint (Annotated) @@ -34,9 +36,9 @@ import Control.DeepSeq import Data.Aeson+import Data.Bifunctor (Bifunctor (..)) import Data.Hashable import Data.String (IsString (fromString))-import Data.Text (Text) -- Orphan instances for types from the GHC API. instance Show CoreModule where show = prettyPrint@@ -93,6 +95,19 @@ rnf = rwhnf #endif +#if MIN_VERSION_ghc(9,2,0)+instance Ord FastString where+ compare a b = if a == b then EQ else compare (fs_sbs a) (fs_sbs b)++instance NFData (SrcSpanAnn' a) where+ rnf = rwhnf++instance Bifunctor (GenLocated) where+ bimap f g (L l x) = L (f l) (g x)++deriving instance Functor SrcSpanAnn'+#endif+ instance NFData ParsedModule where rnf = rwhnf @@ -122,7 +137,7 @@ rnf = rwhnf srcSpanFileTag, srcSpanStartLineTag, srcSpanStartColTag,- srcSpanEndLineTag, srcSpanEndColTag :: Text+ srcSpanEndLineTag, srcSpanEndColTag :: String srcSpanFileTag = "srcSpanFile" srcSpanStartLineTag = "srcSpanStartLine" srcSpanStartColTag = "srcSpanStartCol"@@ -132,24 +147,24 @@ instance ToJSON RealSrcSpan where toJSON spn = object- [ srcSpanFileTag .= unpackFS (srcSpanFile spn)- , srcSpanStartLineTag .= srcSpanStartLine spn- , srcSpanStartColTag .= srcSpanStartCol spn- , srcSpanEndLineTag .= srcSpanEndLine spn- , srcSpanEndColTag .= srcSpanEndCol spn+ [ fromString srcSpanFileTag .= unpackFS (srcSpanFile spn)+ , fromString srcSpanStartLineTag .= srcSpanStartLine spn+ , fromString srcSpanStartColTag .= srcSpanStartCol spn+ , fromString srcSpanEndLineTag .= srcSpanEndLine spn+ , fromString srcSpanEndColTag .= srcSpanEndCol spn ] instance FromJSON RealSrcSpan where parseJSON = withObject "object" $ \obj -> do- file <- fromString <$> (obj .: srcSpanFileTag)+ file <- fromString <$> (obj .: fromString srcSpanFileTag) mkRealSrcSpan <$> (mkRealSrcLoc file- <$> obj .: srcSpanStartLineTag- <*> obj .: srcSpanStartColTag+ <$> obj .: fromString srcSpanStartLineTag+ <*> obj .: fromString srcSpanStartColTag ) <*> (mkRealSrcLoc file- <$> obj .: srcSpanEndLineTag- <*> obj .: srcSpanEndColTag+ <$> obj .: fromString srcSpanEndLineTag+ <*> obj .: fromString srcSpanEndColTag ) instance NFData Type where
src/Development/IDE/GHC/Util.hs view
@@ -28,49 +28,25 @@ setHieDir, dontWriteHieFiles, disableWarningsAsErrors,- ) where+ traceAst) where #if MIN_VERSION_ghc(9,2,0)-import GHC-import GHC.Core.Multiplicity-import qualified GHC.Core.TyCo.Rep as TyCoRep import GHC.Data.FastString import GHC.Data.StringBuffer import GHC.Driver.Env-import GHC.Driver.Env.Types import GHC.Driver.Monad import GHC.Driver.Session hiding (ExposePackage)-import qualified GHC.Driver.Session as DynFlags-import GHC.Hs.Extension-import qualified GHC.Hs.Type as GHC-import GHC.Iface.Env (updNameCache)-import GHC.Iface.Make (mkIfaceExports)-import qualified GHC.Linker.Types as LinkerTypes import GHC.Parser.Lexer import GHC.Runtime.Context-import GHC.Tc.Types (TcGblEnv (tcg_exports))-import GHC.Tc.Utils.TcType (pprSigmaType)-import GHC.Types.Avail-import GHC.Types.Name.Cache import GHC.Types.Name.Occurrence import GHC.Types.Name.Reader import GHC.Types.SrcLoc-import qualified GHC.Types.SrcLoc as SrcLoc-import GHC.Unit.Env-import GHC.Unit.Info (PackageName)-import qualified GHC.Unit.Info as Packages-import qualified GHC.Unit.Module.Location as Module import GHC.Unit.Module.ModDetails import GHC.Unit.Module.ModGuts-import GHC.Unit.Module.ModIface (mi_mod_hash)-import GHC.Unit.Module.Name (moduleNameSlashes)-import qualified GHC.Unit.State as Packages-import GHC.Unit.Types (IsBootInterface (..),- unitString)-import qualified GHC.Unit.Types as Module import GHC.Utils.Fingerprint import GHC.Utils.Outputable-import qualified GHC.Utils.Outputable as Outputable+#else+import Development.IDE.GHC.Compat.Util #endif import Control.Concurrent import Control.Exception as E@@ -79,17 +55,22 @@ import Data.ByteString.Internal (ByteString (..)) import qualified Data.ByteString.Internal as BS import qualified Data.ByteString.Lazy as LBS+import Data.Data (Data) import Data.IORef import Data.List.Extra import Data.Maybe import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Encoding.Error as T+import Data.Time.Clock.POSIX (POSIXTime, getCurrentTime,+ utcTimeToPOSIXSeconds) import Data.Typeable+import qualified Data.Unique as U+import Debug.Trace import Development.IDE.GHC.Compat as GHC import qualified Development.IDE.GHC.Compat.Parser as Compat import qualified Development.IDE.GHC.Compat.Units as Compat-import Development.IDE.GHC.Compat.Util+import Development.IDE.GHC.Dump (showAstDataHtml) import Development.IDE.Types.Location import Foreign.ForeignPtr import Foreign.Ptr@@ -101,8 +82,11 @@ import GHC.IO.Exception import GHC.IO.Handle.Internals import GHC.IO.Handle.Types-+import GHC.Stack+import System.Environment.Blank (getEnvDefault) import System.FilePath+import System.IO.Unsafe+import Text.Printf ----------------------------------------------------------------------@@ -300,3 +284,38 @@ ioe_dupHandlesNotCompatible h = ioException (IOError (Just h) IllegalOperation "hDuplicateTo" "handles are incompatible" Nothing Nothing)++--------------------------------------------------------------------------------+-- Tracing exactprint terms++{-# NOINLINE timestamp #-}+timestamp :: POSIXTime+timestamp = utcTimeToPOSIXSeconds $ unsafePerformIO getCurrentTime++debugAST :: Bool+debugAST = unsafePerformIO (getEnvDefault "GHCIDE_DEBUG_AST" "0") == "1"++-- | Prints an 'Outputable' value to stderr and to an HTML file for further inspection+traceAst :: (Data a, ExactPrint a, Outputable a, HasCallStack) => String -> a -> a+traceAst lbl x+ | debugAST = trace doTrace x+ | otherwise = x+ where+#if MIN_VERSION_ghc(9,2,0)+ renderDump = renderWithContext defaultSDocContext{sdocStyle = defaultDumpStyle, sdocPprDebug = True}+#else+ renderDump = unsafePrintSDoc+#endif+ htmlDump = showAstDataHtml x+ doTrace = unsafePerformIO $ do+ u <- U.newUnique+ let htmlDumpFileName = printf "/tmp/hls/%s-%s-%d.html" (show timestamp) lbl (U.hashUnique u)+ writeFile htmlDumpFileName $ renderDump htmlDump+ return $ unlines+ [prettyCallStack callStack ++ ":"+#if MIN_VERSION_ghc(9,2,0)+ , exactPrint x+#endif+ , "file://" ++ htmlDumpFileName]++
src/Development/IDE/Import/DependencyInformation.hs view
@@ -166,7 +166,7 @@ instance NFData ModuleParseError -- | Error when trying to locate a module.-data LocateError = LocateError [Diagnostic]+newtype LocateError = LocateError [Diagnostic] deriving (Eq, Show, Generic) instance NFData LocateError@@ -316,7 +316,7 @@ where go :: Int -> IntSet -> IntSet go k i =- let outwards = fromMaybe IntSet.empty (IntMap.lookup k depReverseModuleDeps)+ let outwards = IntMap.findWithDefault IntSet.empty k depReverseModuleDeps res = IntSet.union i outwards new = IntSet.difference i outwards in IntSet.foldr go res new
src/Development/IDE/LSP/LanguageServer.hs view
@@ -38,8 +38,16 @@ import Development.IDE.LSP.HoverDefinition import Development.IDE.Types.Logger +import Control.Monad.IO.Unlift (MonadUnliftIO)+import Development.IDE.Types.Shake (WithHieDb) import System.IO.Unsafe (unsafeInterleaveIO) +issueTrackerUrl :: T.Text+issueTrackerUrl = "https://github.com/haskell/haskell-language-server/issues"++-- used to smuggle RankNType WithHieDb through dbMVar+newtype WithHieDbShield = WithHieDbShield WithHieDb+ runLanguageServer :: forall config. (Show config) => LSP.Options@@ -49,16 +57,21 @@ -> config -> (config -> Value -> Either T.Text config) -> LSP.Handlers (ServerM config)- -> (LSP.LanguageContextEnv config -> VFSHandle -> Maybe FilePath -> HieDb -> IndexQueue -> IO IdeState)+ -> (LSP.LanguageContextEnv config -> VFSHandle -> Maybe FilePath -> WithHieDb -> IndexQueue -> IO IdeState) -> IO () runLanguageServer options inH outH getHieDbLoc defaultConfig onConfigurationChange userHandlers getIdeState = do -- This MVar becomes full when the server thread exits or we receive exit message from client.- -- LSP loop will be canceled when it's full.+ -- LSP server will be canceled when it's full. clientMsgVar <- newEmptyMVar -- Forcefully exit let exit = void $ tryPutMVar clientMsgVar () + -- An MVar to control the lifetime of the reactor loop.+ -- The loop will be stopped and resources freed when it's full+ reactorLifetime <- newEmptyMVar+ let stopReactorLoop = void $ tryPutMVar reactorLifetime ()+ -- The set of requests ids that we have received but not finished processing pendingRequests <- newTVarIO Set.empty -- The set of requests that have been cancelled and are also in pendingRequests@@ -93,6 +106,7 @@ [ ideHandlers , cancelHandler cancelRequest , exitHandler exit+ , shutdownHandler stopReactorLoop ] -- Cancel requests are special since they need to be handled -- out of order to be useful. Existing handlers are run afterwards.@@ -101,25 +115,23 @@ let serverDefinition = LSP.ServerDefinition { LSP.onConfigurationChange = onConfigurationChange , LSP.defaultConfig = defaultConfig- , LSP.doInitialize = handleInit exit clearReqId waitForCancel clientMsgChan+ , LSP.doInitialize = handleInit reactorLifetime exit clearReqId waitForCancel clientMsgChan , LSP.staticHandlers = asyncHandlers , LSP.interpretHandler = \(env, st) -> LSP.Iso (LSP.runLspT env . flip runReaderT (clientMsgChan,st)) liftIO , LSP.options = modifyOptions options } - void $ waitAnyCancel =<< traverse async- [ void $ LSP.runServerWithHandles+ void $ untilMVar clientMsgVar $+ void $ LSP.runServerWithHandles inH outH serverDefinition- , void $ readMVar clientMsgVar- ] where handleInit- :: IO () -> (SomeLspId -> IO ()) -> (SomeLspId -> IO ()) -> Chan ReactorMessage+ :: MVar () -> IO () -> (SomeLspId -> IO ()) -> (SomeLspId -> IO ()) -> Chan ReactorMessage -> LSP.LanguageContextEnv config -> RequestMessage Initialize -> IO (Either err (LSP.LanguageContextEnv config, IdeState))- handleInit exitClientMsg clearReqId waitForCancel clientMsgChan env (RequestMessage _ _ m params) = otTracedHandler "Initialize" (show m) $ \sp -> do+ handleInit lifetime exitClientMsg clearReqId waitForCancel clientMsgChan env (RequestMessage _ _ m params) = otTracedHandler "Initialize" (show m) $ \sp -> do traceWithSpan sp params let root = LSP.resRootPath env dir <- maybe getCurrentDirectory return root@@ -128,70 +140,88 @@ -- The database needs to be open for the duration of the reactor thread, but we need to pass in a reference -- to 'getIdeState', so we use this dirty trick dbMVar <- newEmptyMVar- ~(hiedb,hieChan) <- unsafeInterleaveIO $ takeMVar dbMVar+ ~(WithHieDbShield withHieDb,hieChan) <- unsafeInterleaveIO $ takeMVar dbMVar - ide <- getIdeState env (makeLSPVFSHandle env) root hiedb hieChan+ ide <- getIdeState env (makeLSPVFSHandle env) root withHieDb hieChan let initConfig = parseConfiguration params logInfo (ideLogger ide) $ T.pack $ "Registering ide configuration: " <> show initConfig registerIdeConfiguration (shakeExtras ide) initConfig let handleServerException (Left e) = do- logError (ideLogger ide) $+ logError logger $ T.pack $ "Fatal error in server thread: " <> show e+ sendErrorMessage e exitClientMsg- handleServerException _ = pure ()+ handleServerException (Right _) = pure ()++ sendErrorMessage (e :: SomeException) = do+ LSP.runLspT env $ LSP.sendNotification SWindowShowMessage $+ ShowMessageParams MtError $ T.unlines+ [ "Unhandled exception, please [report](" <> issueTrackerUrl <> "): "+ , T.pack(show e)+ ]++ exceptionInHandler e = do+ logError logger $ T.pack $+ "Unexpected exception, please report!\n" +++ "Exception: " ++ show e+ sendErrorMessage e+ logger = ideLogger ide- _ <- flip forkFinally handleServerException $ runWithDb logger dbLoc $ \hiedb hieChan -> do- putMVar dbMVar (hiedb,hieChan)- forever $ do- msg <- readChan clientMsgChan- -- We dispatch notifications synchronously and requests asynchronously- -- This is to ensure that all file edits and config changes are applied before a request is handled- case msg of- ReactorNotification act -> do- catch act $ \(e :: SomeException) ->- logError (ideLogger ide) $ T.pack $- "Unexpected exception on notification, please report!\n" ++- "Exception: " ++ show e- ReactorRequest _id act k -> void $ async $- checkCancelled ide clearReqId waitForCancel _id act k++ checkCancelled _id act k =+ flip finally (clearReqId _id) $+ catch (do+ -- We could optimize this by first checking if the id+ -- is in the cancelled set. However, this is unlikely to be a+ -- bottleneck and the additional check might hide+ -- issues with async exceptions that need to be fixed.+ cancelOrRes <- race (waitForCancel _id) act+ case cancelOrRes of+ Left () -> do+ logDebug (ideLogger ide) $ T.pack $ "Cancelled request " <> show _id+ k $ ResponseError RequestCancelled "" Nothing+ Right res -> pure res+ ) $ \(e :: SomeException) -> do+ exceptionInHandler e+ k $ ResponseError InternalError (T.pack $ show e) Nothing+ _ <- flip forkFinally handleServerException $ do+ untilMVar lifetime $ runWithDb logger dbLoc $ \withHieDb hieChan -> do+ putMVar dbMVar (WithHieDbShield withHieDb,hieChan)+ forever $ do+ msg <- readChan clientMsgChan+ -- We dispatch notifications synchronously and requests asynchronously+ -- This is to ensure that all file edits and config changes are applied before a request is handled+ case msg of+ ReactorNotification act -> handle exceptionInHandler act+ ReactorRequest _id act k -> void $ async $ checkCancelled _id act k+ logInfo logger "Reactor thread stopped" pure $ Right (env,ide) - checkCancelled- :: IdeState -> (SomeLspId -> IO ()) -> (SomeLspId -> IO ()) -> SomeLspId- -> IO () -> (ResponseError -> IO ()) -> IO ()- checkCancelled ide clearReqId waitForCancel _id act k =- flip finally (clearReqId _id) $- catch (do- -- We could optimize this by first checking if the id- -- is in the cancelled set. However, this is unlikely to be a- -- bottleneck and the additional check might hide- -- issues with async exceptions that need to be fixed.- cancelOrRes <- race (waitForCancel _id) act- case cancelOrRes of- Left () -> do- logDebug (ideLogger ide) $ T.pack $ "Cancelled request " <> show _id- k $ ResponseError RequestCancelled "" Nothing- Right res -> pure res- ) $ \(e :: SomeException) -> do- logError (ideLogger ide) $ T.pack $- "Unexpected exception on request, please report!\n" ++- "Exception: " ++ show e- k $ ResponseError InternalError (T.pack $ show e) Nothing +-- | Runs the action until it ends or until the given MVar is put.+-- Rethrows any exceptions.+untilMVar :: MonadUnliftIO m => MVar () -> m () -> m ()+untilMVar mvar io = void $+ waitAnyCancel =<< traverse async [ io , readMVar mvar ] cancelHandler :: (SomeLspId -> IO ()) -> LSP.Handlers (ServerM c) cancelHandler cancelRequest = LSP.notificationHandler SCancelRequest $ \NotificationMessage{_params=CancelParams{_id}} -> liftIO $ cancelRequest (SomeLspId _id) -exitHandler :: IO () -> LSP.Handlers (ServerM c)-exitHandler exit = LSP.notificationHandler SExit $ const $ do+shutdownHandler :: IO () -> LSP.Handlers (ServerM c)+shutdownHandler stopReactor = LSP.requestHandler SShutdown $ \_ resp -> do (_, ide) <- ask- liftIO $ logDebug (ideLogger ide) "Received exit message"+ liftIO $ logDebug (ideLogger ide) "Received shutdown message"+ -- stop the reactor to free up the hiedb connection+ liftIO stopReactor -- flush out the Shake session to record a Shake profile if applicable liftIO $ shakeShut ide- liftIO exit+ resp $ Right Empty++exitHandler :: IO () -> LSP.Handlers (ServerM c)+exitHandler exit = LSP.notificationHandler SExit $ const $ liftIO exit modifyOptions :: LSP.Options -> LSP.Options modifyOptions x = x{ LSP.textDocumentSync = Just $ tweakTDS origTDS
src/Development/IDE/LSP/Notifications.hs view
@@ -14,6 +14,7 @@ import Language.LSP.Types import qualified Language.LSP.Types as LSP +import Control.Concurrent.STM.Stats (atomically) import Control.Monad.Extra import Control.Monad.IO.Class import qualified Data.HashMap.Strict as HM@@ -42,7 +43,7 @@ descriptor plId = (defaultPluginDescriptor plId) { pluginNotificationHandlers = mconcat [ mkPluginNotificationHandler LSP.STextDocumentDidOpen $ \ide _ (DidOpenTextDocumentParams TextDocumentItem{_uri,_version}) -> liftIO $ do- updatePositionMapping ide (VersionedTextDocumentIdentifier _uri (Just _version)) (List [])+ atomically $ updatePositionMapping ide (VersionedTextDocumentIdentifier _uri (Just _version)) (List []) whenUriFile _uri $ \file -> do -- We don't know if the file actually exists, or if the contents match those on disk -- For example, vscode restores previously unsaved contents on open@@ -52,7 +53,7 @@ , mkPluginNotificationHandler LSP.STextDocumentDidChange $ \ide _ (DidChangeTextDocumentParams identifier@VersionedTextDocumentIdentifier{_uri} changes) -> liftIO $ do- updatePositionMapping ide identifier changes+ atomically $ updatePositionMapping ide identifier changes whenUriFile _uri $ \file -> do addFileOfInterest ide file Modified{firstOpen=False} setFileModified ide False file
src/Development/IDE/LSP/Outline.hs view
@@ -29,6 +29,9 @@ SymbolKind (SkConstructor, SkField, SkFile, SkFunction, SkInterface, SkMethod, SkModule, SkObject, SkStruct, SkTypeParameter, SkUnknown), TextDocumentIdentifier (TextDocumentIdentifier), type (|?) (InL), uriToFilePath)+#if MIN_VERSION_ghc(9,2,0)+import Data.List.NonEmpty (nonEmpty, toList)+#endif moduleOutline :: IdeState -> DocumentSymbolParams -> LspM c (Either ResponseError (List DocumentSymbol |? List SymbolInformation))@@ -42,17 +45,11 @@ -> let declSymbols = mapMaybe documentSymbolForDecl hsmodDecls moduleSymbol = hsmodName >>= \case- (L (RealSrcSpan l _) m) -> Just $+ (L (locA -> (RealSrcSpan l _)) m) -> Just $ (defDocumentSymbol l :: DocumentSymbol) { _name = pprText m , _kind = SkFile- , _range = Range (Position 0 0) (Position 2147483647 0) -- _ltop is 0 0 0 0- -- In the lsp spec from 3.16 Position takes a uinteger,- -- where uinteger is 0 - 2^31 - 1. lsp-types currently has the type of line- -- as Int. So instead of using `maxBound :: Int` we hardcode the maxBound of- -- uinteger. 2 ^ 31 - 1 == 2147483647- -- Check this issue for tracking https://github.com/haskell/lsp/issues/354- -- the change in lsp-types.+ , _range = Range (Position 0 0) (Position maxBound 0) -- _ltop is 0 0 0 0 } _ -> Nothing importSymbols = maybe [] pure $@@ -70,8 +67,8 @@ Nothing -> pure $ Right $ InL (List []) -documentSymbolForDecl :: Located (HsDecl GhcPs) -> Maybe DocumentSymbol-documentSymbolForDecl (L (RealSrcSpan l _) (TyClD _ FamDecl { tcdFam = FamilyDecl { fdLName = L _ n, fdInfo, fdTyVars } }))+documentSymbolForDecl :: LHsDecl GhcPs -> Maybe DocumentSymbol+documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (TyClD _ FamDecl { tcdFam = FamilyDecl { fdLName = L _ n, fdInfo, fdTyVars } })) = Just (defDocumentSymbol l :: DocumentSymbol) { _name = showRdrName n <> (case pprText fdTyVars of@@ -81,7 +78,7 @@ , _detail = Just $ pprText fdInfo , _kind = SkFunction }-documentSymbolForDecl (L (RealSrcSpan l _) (TyClD _ ClassDecl { tcdLName = L _ name, tcdSigs, tcdTyVars }))+documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (TyClD _ ClassDecl { tcdLName = L _ name, tcdSigs, tcdTyVars })) = Just (defDocumentSymbol l :: DocumentSymbol) { _name = showRdrName name <> (case pprText tcdTyVars of@@ -97,11 +94,11 @@ , _kind = SkMethod , _selectionRange = realSrcSpanToRange l' }- | L (RealSrcSpan l _) (ClassOpSig _ False names _) <- tcdSigs- , L (RealSrcSpan l' _) n <- names+ | L (locA -> (RealSrcSpan l _)) (ClassOpSig _ False names _) <- tcdSigs+ , L (locA -> (RealSrcSpan l' _)) n <- names ] }-documentSymbolForDecl (L (RealSrcSpan l _) (TyClD _ DataDecl { tcdLName = L _ name, tcdDataDefn = HsDataDefn { dd_cons } }))+documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (TyClD _ DataDecl { tcdLName = L _ name, tcdDataDefn = HsDataDefn { dd_cons } })) = Just (defDocumentSymbol l :: DocumentSymbol) { _name = showRdrName name , _kind = SkStruct@@ -111,13 +108,33 @@ { _name = showRdrName n , _kind = SkConstructor , _selectionRange = realSrcSpanToRange l'- , _children = conArgRecordFields (con_args x)+#if MIN_VERSION_ghc(9,2,0)+ , _children = List . toList <$> nonEmpty childs }- | L (RealSrcSpan l _ ) x <- dd_cons- , L (RealSrcSpan l' _) n <- getConNames' x+ | con <- dd_cons+ , let (cs, flds) = hsConDeclsBinders con+ , let childs = mapMaybe cvtFld flds+ , L (locA -> RealSrcSpan l' _) n <- cs+ , let l = case con of+ L (locA -> RealSrcSpan l _) _ -> l+ _ -> l' ] } where+ cvtFld :: LFieldOcc GhcPs -> Maybe DocumentSymbol+ cvtFld (L (RealSrcSpan l _) n) = Just $ (defDocumentSymbol l :: DocumentSymbol)+ { _name = showRdrName (unLoc (rdrNameFieldOcc n))+ , _kind = SkField+ }+ cvtFld _ = Nothing+#else+ , _children = conArgRecordFields (con_args x)+ }+ | L (locA -> (RealSrcSpan l _ )) x <- dd_cons+ , L (locA -> (RealSrcSpan l' _)) n <- getConNames' x+ ]+ }+ where -- | Extract the record fields of a constructor conArgRecordFields (RecCon (L _ lcdfs)) = Just $ List [ (defDocumentSymbol l :: DocumentSymbol)@@ -125,48 +142,57 @@ , _kind = SkField } | L _ cdf <- lcdfs- , L (RealSrcSpan l _) n <- rdrNameFieldOcc . unLoc <$> cd_fld_names cdf+ , L (locA -> (RealSrcSpan l _)) n <- rdrNameFieldOcc . unLoc <$> cd_fld_names cdf ] conArgRecordFields _ = Nothing-documentSymbolForDecl (L (RealSrcSpan l _) (TyClD _ SynDecl { tcdLName = L (RealSrcSpan l' _) n })) = Just+#endif+documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (TyClD _ SynDecl { tcdLName = L (locA -> (RealSrcSpan l' _)) n })) = Just (defDocumentSymbol l :: DocumentSymbol) { _name = showRdrName n , _kind = SkTypeParameter , _selectionRange = realSrcSpanToRange l' }-documentSymbolForDecl (L (RealSrcSpan l _) (InstD _ ClsInstD { cid_inst = ClsInstDecl { cid_poly_ty } }))+documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (InstD _ ClsInstD { cid_inst = ClsInstDecl { cid_poly_ty } })) = Just (defDocumentSymbol l :: DocumentSymbol) { _name = pprText cid_poly_ty , _kind = SkInterface }+#if MIN_VERSION_ghc(9,2,0)+documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (InstD _ DataFamInstD { dfid_inst = DataFamInstDecl FamEqn { feqn_tycon, feqn_pats } }))+#else documentSymbolForDecl (L (RealSrcSpan l _) (InstD _ DataFamInstD { dfid_inst = DataFamInstDecl HsIB { hsib_body = FamEqn { feqn_tycon, feqn_pats } } }))+#endif = Just (defDocumentSymbol l :: DocumentSymbol) { _name = showRdrName (unLoc feqn_tycon) <> " " <> T.unwords (map pprText feqn_pats) , _kind = SkInterface }+#if MIN_VERSION_ghc(9,2,0)+documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (InstD _ TyFamInstD { tfid_inst = TyFamInstDecl _ FamEqn { feqn_tycon, feqn_pats } }))+#else documentSymbolForDecl (L (RealSrcSpan l _) (InstD _ TyFamInstD { tfid_inst = TyFamInstDecl HsIB { hsib_body = FamEqn { feqn_tycon, feqn_pats } } }))+#endif = Just (defDocumentSymbol l :: DocumentSymbol) { _name = showRdrName (unLoc feqn_tycon) <> " " <> T.unwords (map pprText feqn_pats) , _kind = SkInterface }-documentSymbolForDecl (L (RealSrcSpan l _) (DerivD _ DerivDecl { deriv_type })) =+documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (DerivD _ DerivDecl { deriv_type })) = gfindtype deriv_type <&> \(L (_ :: SrcSpan) name) -> (defDocumentSymbol l :: DocumentSymbol) { _name = pprText @(HsType GhcPs) name , _kind = SkInterface }-documentSymbolForDecl (L (RealSrcSpan l _) (ValD _ FunBind{fun_id = L _ name})) = Just+documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (ValD _ FunBind{fun_id = L _ name})) = Just (defDocumentSymbol l :: DocumentSymbol) { _name = showRdrName name , _kind = SkFunction }-documentSymbolForDecl (L (RealSrcSpan l _) (ValD _ PatBind{pat_lhs})) = Just+documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (ValD _ PatBind{pat_lhs})) = Just (defDocumentSymbol l :: DocumentSymbol) { _name = pprText pat_lhs , _kind = SkFunction } -documentSymbolForDecl (L (RealSrcSpan l _) (ForD _ x)) = Just+documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (ForD _ x)) = Just (defDocumentSymbol l :: DocumentSymbol) { _name = case x of ForeignImport{} -> name@@ -199,8 +225,8 @@ , _children = Just (List importSymbols) } -documentSymbolForImport :: Located (ImportDecl GhcPs) -> Maybe DocumentSymbol-documentSymbolForImport (L (RealSrcSpan l _) ImportDecl { ideclName, ideclQualified }) = Just+documentSymbolForImport :: LImportDecl GhcPs -> Maybe DocumentSymbol+documentSymbolForImport (L (locA -> (RealSrcSpan l _)) ImportDecl { ideclName, ideclQualified }) = Just (defDocumentSymbol l :: DocumentSymbol) { _name = "import " <> pprText ideclName , _kind = SkModule@@ -230,6 +256,7 @@ pprText = pack . showSDocUnsafe . ppr -- the version of getConNames for ghc9 is restricted to only the renaming phase+#if !MIN_VERSION_ghc(9,2,0) getConNames' :: ConDecl GhcPs -> [Located (IdP GhcPs)] getConNames' ConDeclH98 {con_name = name} = [name] getConNames' ConDeclGADT {con_names = names} = names@@ -237,4 +264,45 @@ getConNames' (XConDecl NoExt) = [] #elif !MIN_VERSION_ghc(9,0,0) getConNames' (XConDecl x) = noExtCon x+#endif+#else+hsConDeclsBinders :: LConDecl GhcPs+ -> ([LIdP GhcPs], [LFieldOcc GhcPs])+ -- See hsLTyClDeclBinders for what this does+ -- The function is boringly complicated because of the records+ -- And since we only have equality, we have to be a little careful+hsConDeclsBinders cons+ = go cons+ where+ go :: LConDecl GhcPs+ -> ([LIdP GhcPs], [LFieldOcc GhcPs])+ go r+ -- Don't re-mangle the location of field names, because we don't+ -- have a record of the full location of the field declaration anyway+ = case unLoc r of+ -- remove only the first occurrence of any seen field in order to+ -- avoid circumventing detection of duplicate fields (#9156)+ ConDeclGADT { con_names = names, con_g_args = args }+ -> (names, flds)+ where+ flds = get_flds_gadt args++ ConDeclH98 { con_name = name, con_args = args }+ -> ([name], flds)+ where+ flds = get_flds_h98 args++ get_flds_h98 :: HsConDeclH98Details GhcPs+ -> [LFieldOcc GhcPs]+ get_flds_h98 (RecCon flds) = get_flds (reLoc flds)+ get_flds_h98 _ = []++ get_flds_gadt :: HsConDeclGADTDetails GhcPs+ -> ([LFieldOcc GhcPs])+ get_flds_gadt (RecConGADT flds) = get_flds (reLoc flds)+ get_flds_gadt _ = []++ get_flds :: Located [LConDeclField GhcPs]+ -> ([LFieldOcc GhcPs])+ get_flds flds = concatMap (cd_fld_names . unLoc) (unLoc flds) #endif
src/Development/IDE/Main.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE PackageImports #-} {-# OPTIONS_GHC -Wno-orphans #-} module Development.IDE.Main (Arguments(..)@@ -8,9 +9,10 @@ ,commandP ,defaultMain ,testing) where-import Control.Concurrent.Extra (newLock, readVar,- withLock,+import Control.Concurrent.Extra (newLock, withLock, withNumCapabilities)+import Control.Concurrent.STM.Stats (atomically,+ dumpSTMStats) import Control.Exception.Safe (Exception (displayException), catchAny) import Control.Monad.Extra (concatMapM, unless,@@ -55,6 +57,7 @@ import Development.IDE.Core.Tracing (measureMemory) import Development.IDE.Graph (action) import Development.IDE.LSP.LanguageServer (runLanguageServer)+import Development.IDE.Main.HeapStats (withHeapStats) import Development.IDE.Plugin (Plugin (pluginHandlers, pluginModifyDynflags, pluginRules)) import Development.IDE.Plugin.HLS (asGhcIdePlugin) import qualified Development.IDE.Plugin.HLS.GhcIde as Ghcide@@ -62,6 +65,7 @@ import Development.IDE.Session (SessionLoadingOptions, getHieDbLoc, loadSessionWithOptions,+ retryOnSqliteBusy, runWithDb, setInitialDynFlags) import Development.IDE.Types.Location (NormalizedUri,@@ -98,8 +102,10 @@ PluginId (PluginId), ipMap) import qualified Language.LSP.Server as LSP+import qualified "list-t" ListT import Numeric.Natural (Natural) import Options.Applicative hiding (action)+import qualified StmContainers.Map as STM import qualified System.Directory.Extra as IO import System.Exit (ExitCode (ExitFailure), exitWith)@@ -111,18 +117,19 @@ hSetBuffering, hSetEncoding, stderr, stdin, stdout, utf8)+import System.Random (newStdGen) import System.Time.Extra (offsetTime, showDuration) import Text.Printf (printf) data Command = Check [FilePath] -- ^ Typecheck some paths and print diagnostics. Exit code is the number of failures- | Db {projectRoot :: FilePath, hieOptions :: HieDb.Options, hieCommand :: HieDb.Command}+ | Db {hieOptions :: HieDb.Options, hieCommand :: HieDb.Command} -- ^ Run a command in the hiedb | LSP -- ^ Run the LSP server | PrintExtensionSchema | PrintDefaultConfig- | Custom {projectRoot :: FilePath, ideCommand :: IdeCommand IdeState} -- ^ User defined+ | Custom {ideCommand :: IdeCommand IdeState} -- ^ User defined deriving Show @@ -137,7 +144,7 @@ commandP :: IdePlugins IdeState -> Parser Command commandP plugins = hsubparser(command "typecheck" (info (Check <$> fileCmd) fileInfo)- <> command "hiedb" (info (Db "." <$> HieDb.optParser "" True <*> HieDb.cmdParser <**> helper) hieInfo)+ <> command "hiedb" (info (Db <$> HieDb.optParser "" True <*> HieDb.cmdParser <**> helper) hieInfo) <> command "lsp" (info (pure LSP <**> helper) lspInfo) <> command "vscode-extension-schema" extensionSchemaCommand <> command "generate-default-config" generateDefaultConfigCommand@@ -156,13 +163,14 @@ (fullDesc <> progDesc "Print config supported by the server with default values") pluginCommands = mconcat- [ command (T.unpack pId) (Custom "." <$> p)+ [ command (T.unpack pId) (Custom <$> p) | (PluginId pId, PluginDescriptor{pluginCli = Just p}) <- ipMap plugins ] data Arguments = Arguments- { argsOTMemoryProfiling :: Bool+ { argsProjectRoot :: Maybe FilePath+ , argsOTMemoryProfiling :: Bool , argCommand :: Command , argsLogger :: IO Logger , argsRules :: Rules ()@@ -184,7 +192,8 @@ defaultArguments :: Priority -> Arguments defaultArguments priority = Arguments- { argsOTMemoryProfiling = False+ { argsProjectRoot = Nothing+ , argsOTMemoryProfiling = False , argCommand = LSP , argsLogger = stderrLogger priority , argsRules = mainRule def >> action kick@@ -237,7 +246,9 @@ T.hPutStrLn stderr $ "[" <> T.pack (show p) <> "] " <> m defaultMain :: Arguments -> IO ()-defaultMain Arguments{..} = do+defaultMain Arguments{..} = flip withHeapStats fun =<< argsLogger+ where+ fun = do setLocaleEncoding utf8 pid <- T.pack . show <$> getProcessID logger <- argsLogger@@ -265,7 +276,7 @@ t <- offsetTime logInfo logger "Starting LSP server..." logInfo logger "If you are seeing this in a terminal, you probably should have run WITHOUT the --lsp option!"- runLanguageServer options inH outH argsGetHieDbLoc argsDefaultHlsConfig argsOnConfigChange (pluginHandlers plugins) $ \env vfs rootPath hiedb hieChan -> do+ runLanguageServer options inH outH argsGetHieDbLoc argsDefaultHlsConfig argsOnConfigChange (pluginHandlers plugins) $ \env vfs rootPath withHieDb hieChan -> do traverse_ IO.setCurrentDirectory rootPath t <- t logInfo logger $ T.pack $ "Started LSP server in " ++ showDuration t@@ -306,10 +317,11 @@ debouncer options vfs- hiedb+ withHieDb hieChan+ dumpSTMStats Check argFiles -> do- dir <- IO.getCurrentDirectory+ dir <- maybe IO.getCurrentDirectory return argsProjectRoot dbLoc <- getHieDbLoc dir runWithDb logger dbLoc $ \hiedb hieChan -> do -- GHC produces messages with UTF8 in them, so make sure the terminal doesn't error@@ -357,31 +369,34 @@ putStrLn $ "\nCompleted (" ++ nfiles worked ++ " worked, " ++ nfiles failed ++ " failed)" when argsOTMemoryProfiling $ do- let valuesRef = state $ shakeExtras ide- values <- readVar valuesRef+ let values = state $ shakeExtras ide let consoleObserver Nothing = return $ \size -> printf "Total: %.2fMB\n" (fromIntegral @Int @Double size / 1e6) consoleObserver (Just k) = return $ \size -> printf " - %s: %.2fKB\n" (show k) (fromIntegral @Int @Double size / 1e3) - printf "# Shake value store contents(%d):\n" (length values)+ stateContents <- atomically $ ListT.toList $ STM.listT values+ printf "# Shake value store contents(%d):\n" (length stateContents) let keys = nub $ typeOf GhcSession : typeOf GhcSessionDeps :- [kty | (fromKeyType -> Just (kty,_)) <- HashMap.keys values, kty /= typeOf GhcSessionIO] +++ [kty | (fromKeyType -> Just (kty,_), _) <- stateContents, kty /= typeOf GhcSessionIO] ++ [typeOf GhcSessionIO]- measureMemory logger [keys] consoleObserver valuesRef+ measureMemory logger [keys] consoleObserver values unless (null failed) (exitWith $ ExitFailure (length failed))- Db dir opts cmd -> do- dbLoc <- getHieDbLoc dir+ Db opts cmd -> do+ root <- maybe IO.getCurrentDirectory return argsProjectRoot+ dbLoc <- getHieDbLoc root hPutStrLn stderr $ "Using hiedb at: " ++ dbLoc- mlibdir <- setInitialDynFlags logger dir def+ mlibdir <- setInitialDynFlags logger root def+ rng <- newStdGen case mlibdir of Nothing -> exitWith $ ExitFailure 1- Just libdir -> HieDb.runCommand libdir opts{HieDb.database = dbLoc} cmd+ Just libdir -> retryOnSqliteBusy logger rng (HieDb.runCommand libdir opts{HieDb.database = dbLoc} cmd) - Custom projectRoot (IdeCommand c) -> do- dbLoc <- getHieDbLoc projectRoot+ Custom (IdeCommand c) -> do+ root <- maybe IO.getCurrentDirectory return argsProjectRoot+ dbLoc <- getHieDbLoc root runWithDb logger dbLoc $ \hiedb hieChan -> do vfs <- makeVFSHandle sessionLoader <- loadSessionWithOptions argsSessionLoadingOptions "."
+ src/Development/IDE/Main/HeapStats.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE NumericUnderscores #-} +-- | Logging utilities for reporting heap statistics +module Development.IDE.Main.HeapStats ( withHeapStats ) where + +import Control.Concurrent +import Control.Concurrent.Async +import Control.Monad +import qualified Data.Text as T +import Data.Word +import Development.IDE.Types.Logger (Logger, logInfo) +import GHC.Stats +import Text.Printf (printf) + +-- | Interval at which to report the latest heap statistics. +heapStatsInterval :: Int +heapStatsInterval = 60_000_000 -- 60s + +-- | Report the live bytes and heap size at the last major collection. +logHeapStats :: Logger -> IO () +logHeapStats l = do + stats <- getRTSStats + -- live_bytes is the total amount of live memory in a program + -- (corresponding to the amount on a heap profile) + let live_bytes = gcdetails_live_bytes (gc stats) + -- heap_size is the total amount of memory the RTS is using + -- this corresponds closer to OS memory usage + heap_size = gcdetails_mem_in_use_bytes (gc stats) + format :: Word64 -> T.Text + format m = T.pack (printf "%.2fMB" (fromIntegral @Word64 @Double m / 1e6)) + message = "Live bytes: " <> format live_bytes <> " " <> + "Heap size: " <> format heap_size + logInfo l message + +-- | An action which logs heap statistics at the 'heapStatsInterval' +heapStatsThread :: Logger -> IO r +heapStatsThread l = forever $ do + threadDelay heapStatsInterval + logHeapStats l + +-- | A helper function which lauches the 'heapStatsThread' and kills it +-- appropiately when the inner action finishes. It also checks to see +-- if `-T` is enabled. +withHeapStats :: Logger -> IO r -> IO r +withHeapStats l k = do + enabled <- getRTSStatsEnabled + if enabled + then do + logInfo l ("Logging heap statistics every " + <> T.pack (printf "%.2fs" (fromIntegral @Int @Double heapStatsInterval / 1e6))) + withAsync (heapStatsThread l) (const k) + else do + logInfo l "Heap statistics are not enabled (RTS option -T is needed)" + k
src/Development/IDE/Plugin/CodeAction.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GADTs #-} -- | Go to the definition of a variable. @@ -21,7 +22,9 @@ import Control.Applicative ((<|>)) import Control.Arrow (second, (>>>))-import Control.Monad (guard, join)+import Control.Concurrent.STM.Stats (atomically)+import Control.Monad (guard, join,+ msum) import Control.Monad.IO.Class import Data.Char import qualified Data.DList as DL@@ -44,8 +47,10 @@ import Development.IDE.GHC.Compat import Development.IDE.GHC.Compat.Util import Development.IDE.GHC.Error+import Development.IDE.GHC.ExactPrint import Development.IDE.GHC.Util (prettyPrint, printRdrName,+ traceAst, unsafePrintSDoc) import Development.IDE.Plugin.CodeAction.Args import Development.IDE.Plugin.CodeAction.ExactPrint@@ -70,6 +75,7 @@ SMethod (STextDocumentCodeAction), TextDocumentIdentifier (TextDocumentIdentifier), TextEdit (TextEdit),+ UInt, WorkspaceEdit (WorkspaceEdit, _changeAnnotations, _changes, _documentChanges), type (|?) (InR), uriToFilePath)@@ -90,7 +96,7 @@ liftIO $ do let text = Rope.toText . (_text :: VirtualFile -> Rope.Rope) <$> contents mbFile = toNormalizedFilePath' <$> uriToFilePath uri- diag <- fmap (\(_, _, d) -> d) . filter (\(p, _, _) -> mbFile == Just p) <$> getDiagnostics state+ diag <- atomically $ fmap (\(_, _, d) -> d) . filter (\(p, _, _) -> mbFile == Just p) <$> getDiagnostics state (join -> parsedModule) <- runAction "GhcideCodeActions.getParsedModule" state $ getParsedModule `traverse` mbFile let actions = caRemoveRedundantImports parsedModule text diag xs uri@@ -139,7 +145,7 @@ ------------------------------------------------------------------------------------------------- -findSigOfDecl :: (IdP p -> Bool) -> [LHsDecl p] -> Maybe (Sig p)+findSigOfDecl :: p ~ GhcPass p0 => (IdP p -> Bool) -> [LHsDecl p] -> Maybe (Sig p) findSigOfDecl pred decls = listToMaybe [ sig@@ -147,7 +153,7 @@ any (pred . unLoc) idsSig ] -findSigOfDeclRanged :: Range -> [LHsDecl p] -> Maybe (Sig p)+findSigOfDeclRanged :: forall p p0 . p ~ GhcPass p0 => Range -> [LHsDecl p] -> Maybe (Sig p) findSigOfDeclRanged range decls = do dec <- findDeclContainingLoc (_start range) decls case dec of@@ -155,7 +161,7 @@ L _ (ValD _ (bind :: HsBind p)) -> findSigOfBind range bind _ -> Nothing -findSigOfBind :: Range -> HsBind p -> Maybe (Sig p)+findSigOfBind :: forall p p0. p ~ GhcPass p0 => Range -> HsBind p -> Maybe (Sig p) findSigOfBind range bind = case bind of FunBind {} -> findSigOfLMatch (unLoc $ mg_alts (fun_matches bind))@@ -164,30 +170,38 @@ findSigOfLMatch :: [LMatch p (LHsExpr p)] -> Maybe (Sig p) findSigOfLMatch ls = do match <- findDeclContainingLoc (_start range) ls- findSigOfGRHSs (m_grhss (unLoc match))-- findSigOfGRHSs :: GRHSs p (LHsExpr p) -> Maybe (Sig p)- findSigOfGRHSs grhs = do- if _start range `isInsideSrcSpan` (getLoc $ grhssLocalBinds grhs)+ let grhs = m_grhss $ unLoc match+#if !MIN_VERSION_ghc(9,2,0)+ span = getLoc $ reLoc $ grhssLocalBinds grhs+ if _start range `isInsideSrcSpan` span then findSigOfBinds range (unLoc (grhssLocalBinds grhs)) -- where clause else do- grhs <- findDeclContainingLoc (_start range) (grhssGRHSs grhs)+ grhs <- findDeclContainingLoc (_start range) (map reLocA $ grhssGRHSs grhs) case unLoc grhs of GRHS _ _ bd -> findSigOfExpr (unLoc bd) _ -> Nothing+#else+ msum+ [findSigOfBinds range (grhssLocalBinds grhs) -- where clause+ , do+ grhs <- findDeclContainingLoc (_start range) (map reLocA $ grhssGRHSs grhs)+ case unLoc grhs of+ GRHS _ _ bd -> findSigOfExpr (unLoc bd)+ ]+#endif findSigOfExpr :: HsExpr p -> Maybe (Sig p) findSigOfExpr = go where- go (HsLet _ binds _) = findSigOfBinds range (unLoc binds)+ go (HsLet _ binds _) = findSigOfBinds range binds go (HsDo _ _ stmts) = do stmtlr <- unLoc <$> findDeclContainingLoc (_start range) (unLoc stmts) case stmtlr of- LetStmt _ lhsLocalBindsLR -> findSigOfBinds range $ unLoc lhsLocalBindsLR- _ -> Nothing+ LetStmt _ lhsLocalBindsLR -> findSigOfBinds range lhsLocalBindsLR+ _ -> Nothing go _ = Nothing -findSigOfBinds :: Range -> HsLocalBinds p -> Maybe (Sig p)+findSigOfBinds :: p ~ GhcPass p0 => Range -> HsLocalBinds p -> Maybe (Sig p) findSigOfBinds range = go where go (HsValBinds _ (ValBinds _ binds lsigs)) =@@ -198,17 +212,27 @@ findSigOfBind range (unLoc lHsBindLR) go _ = Nothing -findInstanceHead :: (Outputable (HsType p)) => DynFlags -> String -> [LHsDecl p] -> Maybe (LHsType p)+findInstanceHead :: (Outputable (HsType p), p ~ GhcPass p0) => DynFlags -> String -> [LHsDecl p] -> Maybe (LHsType p) findInstanceHead df instanceHead decls = listToMaybe+#if !MIN_VERSION_ghc(9,2,0) [ hsib_body | L _ (InstD _ (ClsInstD _ ClsInstDecl {cid_poly_ty = HsIB {hsib_body}})) <- decls, showSDoc df (ppr hsib_body) == instanceHead ]--findDeclContainingLoc :: Position -> [Located a] -> Maybe (Located a)-findDeclContainingLoc loc = find (\(L l _) -> loc `isInsideSrcSpan` l)+#else+ [ hsib_body+ | L _ (InstD _ (ClsInstD _ ClsInstDecl {cid_poly_ty = (unLoc -> HsSig {sig_body = hsib_body})})) <- decls,+ showSDoc df (ppr hsib_body) == instanceHead+ ]+#endif +#if MIN_VERSION_ghc(9,2,0)+findDeclContainingLoc :: Foldable t => Position -> t (GenLocated (SrcSpanAnn' a) e) -> Maybe (GenLocated (SrcSpanAnn' a) e)+#else+-- TODO populate this type signature for GHC versions <9.2+#endif+findDeclContainingLoc loc = find (\(L l _) -> loc `isInsideSrcSpan` locA l) -- Single: -- This binding for ‘mod’ shadows the existing binding@@ -279,8 +303,8 @@ imv_name == mkModuleName modName, isTheSameLine imv_span importSpan ],- [GRE {..}] <- lookupGlobalRdrEnv rdrEnv occ,- importedIdentifier <- Right gre_name,+ [GRE {gre_name = name}] <- lookupGlobalRdrEnv rdrEnv occ,+ importedIdentifier <- Right name, refs <- M.lookup importedIdentifier refMap = maybe True (not . any (\(_, IdentifierDetails {..}) -> identInfo == S.singleton Use)) refs | otherwise = False@@ -289,7 +313,7 @@ suggestRemoveRedundantImport ParsedModule{pm_parsed_source = L _ HsModule{hsmodImports}} contents Diagnostic{_range=_range,..} -- The qualified import of ‘many’ from module ‘Control.Applicative’ is redundant | Just [_, bindings] <- matchRegexUnifySpaces _message "The( qualified)? import of ‘([^’]*)’ from module [^ ]* is redundant"- , Just (L _ impDecl) <- find (\(L l _) -> _start _range `isInsideSrcSpan` l && _end _range `isInsideSrcSpan` l ) hsmodImports+ , Just (L _ impDecl) <- find (\(L (locA -> l) _) -> _start _range `isInsideSrcSpan` l && _end _range `isInsideSrcSpan` l ) hsmodImports , Just c <- contents , ranges <- map (rangesForBindingImport impDecl . T.unpack) (T.splitOn ", " bindings) , ranges' <- extendAllToIncludeCommaIfPossible False (indexedByPosition $ T.unpack c) (concat ranges)@@ -389,7 +413,7 @@ suggestRemoveRedundantExport ParsedModule{pm_parsed_source = L _ HsModule{..}} Diagnostic{..} | msg <- unifySpaces _message , Just export <- hsmodExports- , Just exportRange <- getLocatedRange export+ , Just exportRange <- getLocatedRange $ reLoc export , exports <- unLoc export , Just (removeFromExport, !ranges) <- fmap (getRanges exports . notInScope) (extractNotInScopeName msg) <|> (,[_range]) <$> matchExportItem msg@@ -417,7 +441,7 @@ | otherwise = [] where relatedRanges indexedContent name =- concatMap (findRelatedSpans indexedContent name) hsmodDecls+ concatMap (findRelatedSpans indexedContent name . reLoc) hsmodDecls toRange = realSrcSpanToRange extendForSpaces = extendToIncludePreviousNewlineIfPossible @@ -432,7 +456,7 @@ findSig _ = [] in extendForSpaces indexedContent (toRange l) :- concatMap findSig hsmodDecls+ concatMap (findSig . reLoc) hsmodDecls _ -> concatMap (findRelatedSpanForMatch indexedContent name) matches findRelatedSpans _ _ _ = [] @@ -443,7 +467,7 @@ FunBind { fun_id=lname , fun_matches=MG {mg_alts=L _ matches}- } = Just (lname, matches)+ } = Just (reLoc lname, matches) extractNameAndMatchesFromFunBind _ = Nothing findRelatedSigSpan :: PositionIndexedString -> String -> RealSrcSpan -> Sig GhcPs -> [Range]@@ -460,16 +484,16 @@ let maybeIdx = findIndex (\(L _ id) -> isSameName id name) lnames in case maybeIdx of Nothing -> Nothing- Just _ | length lnames == 1 -> Just (getLoc $ head lnames, True)+ Just _ | length lnames == 1 -> Just (getLoc $ reLoc $ head lnames, True) Just idx ->- let targetLname = getLoc $ lnames !! idx+ let targetLname = getLoc $ reLoc $ lnames !! idx startLoc = srcSpanStart targetLname endLoc = srcSpanEnd targetLname startLoc' = if idx == 0 then startLoc- else srcSpanEnd . getLoc $ lnames !! (idx - 1)+ else srcSpanEnd . getLoc . reLoc $ lnames !! (idx - 1) endLoc' = if idx == 0 && idx < length lnames - 1- then srcSpanStart . getLoc $ lnames !! (idx + 1)+ then srcSpanStart . getLoc . reLoc $ lnames !! (idx + 1) else endLoc in Just (mkSrcSpan startLoc' endLoc', False) findRelatedSigSpan1 _ _ = Nothing@@ -484,12 +508,19 @@ indexedContent name (L _ Match{m_grhss=GRHSs{grhssLocalBinds}}) = do+ let go bag lsigs =+ if isEmptyBag bag+ then []+ else concatMap (findRelatedSpanForHsBind indexedContent name lsigs) bag+#if !MIN_VERSION_ghc(9,2,0) case grhssLocalBinds of- (L _ (HsValBinds _ (ValBinds _ bag lsigs))) ->- if isEmptyBag bag- then []- else concatMap (findRelatedSpanForHsBind indexedContent name lsigs) bag- _ -> []+ (L _ (HsValBinds _ (ValBinds _ bag lsigs))) -> go bag lsigs+ _ -> []+#else+ case grhssLocalBinds of+ (HsValBinds _ (ValBinds _ bag lsigs)) -> go bag lsigs+ _ -> []+#endif findRelatedSpanForMatch _ _ _ = [] findRelatedSpanForHsBind@@ -502,12 +533,12 @@ indexedContent name lsigs- (L (RealSrcSpan l _) (extractNameAndMatchesFromFunBind -> Just (lname, matches))) =+ (L (locA -> (RealSrcSpan l _)) (extractNameAndMatchesFromFunBind -> Just (lname, matches))) = if isTheBinding (getLoc lname) then let findSig (L (RealSrcSpan l _) sig) = findRelatedSigSpan indexedContent name l sig findSig _ = []- in extendForSpaces indexedContent (toRange l) : concatMap findSig lsigs+ in extendForSpaces indexedContent (toRange l) : concatMap (findSig . reLoc) lsigs else concatMap (findRelatedSpanForMatch indexedContent name) matches findRelatedSpanForHsBind _ _ _ _ = [] @@ -517,7 +548,7 @@ isSameName :: IdP GhcPs -> String -> Bool isSameName x name = showSDocUnsafe (ppr x) == name -data ExportsAs = ExportName | ExportPattern | ExportAll+data ExportsAs = ExportName | ExportPattern | ExportFamily | ExportAll deriving (Eq) getLocatedRange :: Located a -> Maybe Range@@ -534,12 +565,12 @@ <|> matchRegexUnifySpaces _message ".*Defined but not used: data constructor ‘([^ ]+)’" , Just (exportType, _) <- find (matchWithDiagnostic _range . snd) . mapMaybe- (\(L l b) -> if maybe False isTopLevel $ srcSpanToRange l+ (\(L (locA -> l) b) -> if maybe False isTopLevel $ srcSpanToRange l then exportsAs b else Nothing) $ hsmodDecls- , Just pos <- fmap _end . getLocatedRange =<< hsmodExports- , Just needComma <- needsComma source <$> hsmodExports- , let exportName = (if needComma then "," else "") <> printExport exportType name+ , Just pos <- (fmap _end . getLocatedRange) . reLoc =<< hsmodExports+ , Just needComma <- needsComma source <$> fmap reLoc hsmodExports+ , let exportName = (if needComma then ", " else "") <> printExport exportType name insertPos = pos {_character = pred $ _character pos} = [("Export ‘" <> name <> "’", TextEdit (Range insertPos insertPos) exportName)] | otherwise = []@@ -549,7 +580,7 @@ needsComma _ (L _ []) = False needsComma source (L (RealSrcSpan l _) exports) = let closeParan = _end $ realSrcSpanToRange l- lastExport = fmap _end . getLocatedRange $ last exports+ lastExport = fmap _end . getLocatedRange $ last $ fmap reLoc exports in case lastExport of Just lastExport -> not $ T.isInfixOf "," $ textInRange (Range lastExport closeParan) source _ -> False@@ -571,18 +602,19 @@ printExport :: ExportsAs -> T.Text -> T.Text printExport ExportName x = parenthesizeIfNeeds False x printExport ExportPattern x = "pattern " <> x+ printExport ExportFamily x = parenthesizeIfNeeds True x printExport ExportAll x = parenthesizeIfNeeds True x <> "(..)" isTopLevel :: Range -> Bool isTopLevel l = (_character . _start) l == 0 - exportsAs :: HsDecl p -> Maybe (ExportsAs, Located (IdP p))- exportsAs (ValD _ FunBind {fun_id}) = Just (ExportName, fun_id)- exportsAs (ValD _ (PatSynBind _ PSB {psb_id})) = Just (ExportPattern, psb_id)- exportsAs (TyClD _ SynDecl{tcdLName}) = Just (ExportName, tcdLName)- exportsAs (TyClD _ DataDecl{tcdLName}) = Just (ExportAll, tcdLName)- exportsAs (TyClD _ ClassDecl{tcdLName}) = Just (ExportAll, tcdLName)- exportsAs (TyClD _ FamDecl{tcdFam}) = Just (ExportAll, fdLName tcdFam)+ exportsAs :: HsDecl GhcPs -> Maybe (ExportsAs, Located (IdP GhcPs))+ exportsAs (ValD _ FunBind {fun_id}) = Just (ExportName, reLoc fun_id)+ exportsAs (ValD _ (PatSynBind _ PSB {psb_id})) = Just (ExportPattern, reLoc psb_id)+ exportsAs (TyClD _ SynDecl{tcdLName}) = Just (ExportName, reLoc tcdLName)+ exportsAs (TyClD _ DataDecl{tcdLName}) = Just (ExportAll, reLoc tcdLName)+ exportsAs (TyClD _ ClassDecl{tcdLName}) = Just (ExportAll, reLoc tcdLName)+ exportsAs (TyClD _ FamDecl{tcdFam}) = Just (ExportFamily, reLoc $ fdLName tcdFam) exportsAs _ = Nothing suggestAddTypeAnnotationToSatisfyContraints :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]@@ -675,7 +707,7 @@ newDefinitionAction IdeOptions{..} parsedModule Range{_start} name typ | Range _ lastLineP : _ <- [ realSrcSpanToRange sp- | (L l@(RealSrcSpan sp _) _) <- hsmodDecls+ | (L (locA -> l@(RealSrcSpan sp _)) _) <- hsmodDecls , _start `isInsideSrcSpan` l] , nextLineP <- Position{ _line = _line lastLineP + 1, _character = 0} = [ ("Define " <> sig@@ -699,18 +731,33 @@ = [("Use type signature: ‘" <> typeSignature <> "’", TextEdit _range typeSignature)] | otherwise = [] +{- Handles two variants with different formatting++1. Could not find module ‘Data.Cha’+ Perhaps you meant Data.Char (from base-4.12.0.0)++2. Could not find module ‘Data.I’+ Perhaps you meant+ Data.Ix (from base-4.14.3.0)+ Data.Eq (from base-4.14.3.0)+ Data.Int (from base-4.14.3.0)+-} suggestModuleTypo :: Diagnostic -> [(T.Text, TextEdit)] suggestModuleTypo Diagnostic{_range=_range,..}--- src/Development/IDE/Core/Compile.hs:58:1: error:--- Could not find module ‘Data.Cha’--- Perhaps you meant Data.Char (from base-4.12.0.0)- | "Could not find module" `T.isInfixOf` _message- , "Perhaps you meant" `T.isInfixOf` _message = let- findSuggestedModules = map (head . T.words) . drop 2 . T.lines- proposeModule mod = ("replace with " <> mod, TextEdit _range mod)- in map proposeModule $ nubOrd $ findSuggestedModules _message+ | "Could not find module" `T.isInfixOf` _message =+ case T.splitOn "Perhaps you meant" _message of+ [_, stuff] ->+ [ ("replace with " <> modul, TextEdit _range modul)+ | modul <- mapMaybe extractModule (T.lines stuff)+ ]+ _ -> [] | otherwise = []+ where+ extractModule line = case T.words line of+ [modul, "(from", _] -> Just modul+ _ -> Nothing + suggestFillHole :: Diagnostic -> [(T.Text, TextEdit)] suggestFillHole Diagnostic{_range=_range,..} | Just holeName <- extractHoleName _message@@ -847,12 +894,10 @@ Bool -- ^ Parenthesised? ModuleName- deriving (Show) data ModuleTarget = ExistingImp (NonEmpty (LImportDecl GhcPs)) | ImplicitPrelude [LImportDecl GhcPs]- deriving (Show) targetImports :: ModuleTarget -> [LImportDecl GhcPs] targetImports (ExistingImp ne) = NE.toList ne@@ -1005,13 +1050,13 @@ liftParseAST @(HsExpr GhcPs) df $ prettyPrint $ HsVar @GhcPs noExtField $- L (mkGeneralSrcSpan "") rdr+ reLocA $ L (mkGeneralSrcSpan "") rdr else Rewrite (rangeToSrcSpan "<dummy>" _range) $ \df -> liftParseAST @RdrName df $ prettyPrint $ L (mkGeneralSrcSpan "") rdr ] findImportDeclByRange :: [LImportDecl GhcPs] -> Range -> Maybe (LImportDecl GhcPs)-findImportDeclByRange xs range = find (\(L l _)-> srcSpanToRange l == Just range) xs+findImportDeclByRange xs range = find (\(L (locA -> l) _)-> srcSpanToRange l == Just range) xs suggestFixConstructorImport :: Diagnostic -> [(T.Text, TextEdit)] suggestFixConstructorImport Diagnostic{_range=_range,..}@@ -1026,9 +1071,10 @@ = let fixedImport = typ <> "(" <> constructor <> ")" in [("Fix import of " <> fixedImport, TextEdit _range fixedImport)] | otherwise = []+ -- | Suggests a constraint for a declaration for which a constraint is missing. suggestConstraint :: DynFlags -> ParsedSource -> Diagnostic -> [(T.Text, Rewrite)]-suggestConstraint df parsedModule diag@Diagnostic {..}+suggestConstraint df (makeDeltaAst -> parsedModule) diag@Diagnostic {..} | Just missingConstraint <- findMissingConstraint _message = let codeAction = if _message =~ ("the type signature for:" :: String) then suggestFunctionConstraint df parsedModule@@ -1073,14 +1119,18 @@ -- (Pair x x') == (Pair y y') = x == y && x' == y' | Just [instanceLineStr, constraintFirstCharStr] <- matchRegexUnifySpaces _message "bound by the instance declaration at .+:([0-9]+):([0-9]+)"+#if !MIN_VERSION_ghc(9,2,0) , Just (L _ (InstD _ (ClsInstD _ ClsInstDecl {cid_poly_ty = HsIB{hsib_body}})))+#else+ , Just (L _ (InstD _ (ClsInstD _ ClsInstDecl {cid_poly_ty = (unLoc -> HsSig{sig_body = hsib_body})})))+#endif <- findDeclContainingLoc (Position (readPositionNumber instanceLineStr) (readPositionNumber constraintFirstCharStr)) hsmodDecls = Just hsib_body | otherwise = Nothing - readPositionNumber :: T.Text -> Int- readPositionNumber = T.unpack >>> read+ readPositionNumber :: T.Text -> UInt+ readPositionNumber = T.unpack >>> read @Integer >>> fromIntegral actionTitle :: T.Text -> T.Text actionTitle constraint = "Add `" <> constraint@@ -1093,7 +1143,12 @@ suggestImplicitParameter (L _ HsModule {hsmodDecls}) Diagnostic {_message, _range} | Just [implicitT] <- matchRegexUnifySpaces _message "Unbound implicit parameter \\(([^:]+::.+)\\) arising", Just (L _ (ValD _ FunBind {fun_id = L _ funId})) <- findDeclContainingLoc (_start _range) hsmodDecls,- Just (TypeSig _ _ HsWC {hswc_body = HsIB {hsib_body}}) <- findSigOfDecl (== funId) hsmodDecls+#if !MIN_VERSION_ghc(9,2,0)+ Just (TypeSig _ _ HsWC {hswc_body = HsIB {hsib_body}})+#else+ Just (TypeSig _ _ HsWC {hswc_body = (unLoc -> HsSig {sig_body = hsib_body})})+#endif+ <- findSigOfDecl (== funId) hsmodDecls = [( "Add " <> implicitT <> " to the context of " <> T.pack (printRdrName funId) , appendConstraint (T.unpack implicitT) hsib_body)]@@ -1128,7 +1183,11 @@ -- In an equation for ‘eq’: -- eq (Pair x y) (Pair x' y') = x == x' && y == y' | Just typeSignatureName <- findTypeSignatureName _message+#if !MIN_VERSION_ghc(9,2,0) , Just (TypeSig _ _ HsWC{hswc_body = HsIB {hsib_body = sig}})+#else+ , Just (TypeSig _ _ HsWC{hswc_body = (unLoc -> HsSig {sig_body = sig})})+#endif <- findSigOfDecl ((T.unpack typeSignatureName ==) . showSDoc df . ppr) hsmodDecls , title <- actionTitle missingConstraint typeSignatureName = [(title, appendConstraint (T.unpack missingConstraint) sig)]@@ -1151,8 +1210,12 @@ -- Account for both "Redundant constraint" and "Redundant constraints". | "Redundant constraint" `T.isInfixOf` _message , Just typeSignatureName <- findTypeSignatureName _message+#if !MIN_VERSION_ghc(9,2,0) , Just (TypeSig _ _ HsWC{hswc_body = HsIB {hsib_body = sig}})- <- findSigOfDeclRanged _range hsmodDecls+#else+ , Just (TypeSig _ _ HsWC{hswc_body = (unLoc -> HsSig {sig_body = sig})})+#endif+ <- fmap(traceAst "redundantConstraint") $ findSigOfDeclRanged _range hsmodDecls , Just redundantConstraintList <- findRedundantConstraints _message , rewrite <- removeConstraint (toRemove df redundantConstraintList) sig = [(actionTitle redundantConstraintList typeSignatureName, rewrite)]@@ -1171,12 +1234,23 @@ then constraints & T.drop 1 & T.dropEnd 1 & T.strip else constraints +{-+9.2: "message": "/private/var/folders/4m/d38fhm3936x_gy_9883zbq8h0000gn/T/extra-dir-53173393699/Testing.hs:4:1: warning:+ ⢠Redundant constraints: (Eq a, Show a)+ ⢠In the type signature for:+ foo :: forall a. (Eq a, Show a) => a -> Bool",++9.0: "message": "⢠Redundant constraints: (Eq a, Show a)+ ⢠In the type signature for:+ foo :: forall a. (Eq a, Show a) => a -> Bool",+-} findRedundantConstraints :: T.Text -> Maybe [T.Text] findRedundantConstraints t = t & T.lines- & head- & T.strip- & (`matchRegexUnifySpaces` "Redundant constraints?: (.+)")+ -- In <9.2 it's the first line, in 9.2 it' the second line+ & take 2+ & mapMaybe ((`matchRegexUnifySpaces` "Redundant constraints?: (.+)") . T.strip)+ & listToMaybe <&> (head >>> parseConstraints) formatConstraints :: [T.Text] -> T.Text@@ -1289,9 +1363,10 @@ -- * otherwise inserted one line after the last file-header pragma newImportInsertRange :: ParsedSource -> T.Text -> Maybe (Range, Int) newImportInsertRange (L _ HsModule {..}) fileContents- | Just (uncurry Position -> insertPos, col) <- case hsmodImports of- [] -> findPositionNoImports hsmodName hsmodExports fileContents- _ -> findPositionFromImportsOrModuleDecl hsmodImports last True+ | Just ((l, c), col) <- case hsmodImports of+ [] -> findPositionNoImports (fmap reLoc hsmodName) (fmap reLoc hsmodExports) fileContents+ _ -> findPositionFromImportsOrModuleDecl (map reLoc hsmodImports) last True+ , let insertPos = Position (fromIntegral l) (fromIntegral c) = Just (Range insertPos insertPos, col) | otherwise = Nothing @@ -1489,7 +1564,7 @@ in if extend then Range _start (Position (_line _end + 1) 0) else range splitTextAtPosition :: Position -> T.Text -> (T.Text, T.Text)-splitTextAtPosition (Position row col) x+splitTextAtPosition (Position (fromIntegral -> row) (fromIntegral -> col)) x | (preRow, mid:postRow) <- splitAt row $ T.splitOn "\n" x , (preCol, postCol) <- T.splitAt col mid = (T.intercalate "\n" $ preRow ++ [preCol], T.intercalate "\n" $ postCol : postRow)@@ -1497,7 +1572,7 @@ -- | Returns [start .. end[ textInRange :: Range -> T.Text -> T.Text-textInRange (Range (Position startRow startCol) (Position endRow endCol)) text =+textInRange (Range (Position (fromIntegral -> startRow) (fromIntegral -> startCol)) (Position (fromIntegral -> endRow) (fromIntegral -> endCol))) text = case compare startRow endRow of LT -> let (linesInRangeBeforeEndLine, endLineAndFurtherLines) = splitAt (endRow - startRow) linesBeginningWithStartLine@@ -1524,7 +1599,7 @@ wrapOperatorInParens :: String -> String wrapOperatorInParens x = case uncons x of- Just (h, _t) -> if isAlpha h then x else "(" <> x <> ")"+ Just (h, _t) -> if is_ident h then x else "(" <> x <> ")" Nothing -> mempty smallerRangesForBindingExport :: [LIE GhcPs] -> String -> [Range]@@ -1533,22 +1608,35 @@ where unqualify = snd . breakOnEnd "." b' = wrapOperatorInParens . unqualify $ b+#if !MIN_VERSION_ghc(9,2,0) ranges' (L _ (IEThingWith _ thing _ inners labels)) | showSDocUnsafe (ppr thing) == b' = [] | otherwise =- [ l' | L l' x <- inners, showSDocUnsafe (ppr x) == b'] ++- [ l' | L l' x <- labels, showSDocUnsafe (ppr x) == b']+ [ locA l' | L l' x <- inners, showSDocUnsafe (ppr x) == b']+ ++ [ l' | L l' x <- labels, showSDocUnsafe (ppr x) == b']+#else+ ranges' (L _ (IEThingWith _ thing _ inners))+ | showSDocUnsafe (ppr thing) == b' = []+ | otherwise =+ [ locA l' | L l' x <- inners, showSDocUnsafe (ppr x) == b']+#endif ranges' _ = [] rangesForBinding' :: String -> LIE GhcPs -> [SrcSpan]-rangesForBinding' b (L l x@IEVar{}) | showSDocUnsafe (ppr x) == b = [l]-rangesForBinding' b (L l x@IEThingAbs{}) | showSDocUnsafe (ppr x) == b = [l]-rangesForBinding' b (L l (IEThingAll _ x)) | showSDocUnsafe (ppr x) == b = [l]+rangesForBinding' b (L (locA -> l) x@IEVar{}) | showSDocUnsafe (ppr x) == b = [l]+rangesForBinding' b (L (locA -> l) x@IEThingAbs{}) | showSDocUnsafe (ppr x) == b = [l]+rangesForBinding' b (L (locA -> l) (IEThingAll _ x)) | showSDocUnsafe (ppr x) == b = [l]+#if !MIN_VERSION_ghc(9,2,0) rangesForBinding' b (L l (IEThingWith _ thing _ inners labels))+#else+rangesForBinding' b (L (locA -> l) (IEThingWith _ thing _ inners))+#endif | showSDocUnsafe (ppr thing) == b = [l] | otherwise =- [ l' | L l' x <- inners, showSDocUnsafe (ppr x) == b] ++- [ l' | L l' x <- labels, showSDocUnsafe (ppr x) == b]+ [ locA l' | L l' x <- inners, showSDocUnsafe (ppr x) == b]+#if !MIN_VERSION_ghc(9,2,0)+ ++ [ l' | L l' x <- labels, showSDocUnsafe (ppr x) == b]+#endif rangesForBinding' _ _ = [] -- | 'matchRegex' combined with 'unifySpaces'
src/Development/IDE/Plugin/CodeAction/Args.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-} module Development.IDE.Plugin.CodeAction.Args@@ -14,7 +15,7 @@ ) where -import Control.Concurrent.Extra+import Control.Concurrent.STM.Stats (readTVarIO) import Control.Monad.Reader import Control.Monad.Trans.Maybe import Data.Either (fromRight)@@ -54,12 +55,13 @@ runGhcideCodeAction state (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics = List diags}) codeAction = do let mbFile = toNormalizedFilePath' <$> uriToFilePath uri runRule key = runAction ("GhcideCodeActions." <> show key) state $ runMaybeT $ MaybeT (pure mbFile) >>= MaybeT . use key+ caaGhcSession <- onceIO $ runRule GhcSession caaExportsMap <- onceIO $- runRule GhcSession >>= \case+ caaGhcSession >>= \case Just env -> do pkgExports <- envPackageExports env- localExports <- readVar (exportsMap $ shakeExtras state)+ localExports <- readTVarIO (exportsMap $ shakeExtras state) pure $ localExports <> pkgExports _ -> pure mempty caaIdeOptions <- onceIO $ runAction "GhcideCodeActions.getIdeOptions" state getIdeOptions@@ -117,8 +119,12 @@ toTextEdit CodeActionArgs {..} rw = fmap (fromMaybe []) $ runMaybeT $ do df <- MaybeT caaDf+#if !MIN_VERSION_ghc(9,2,0) ps <- MaybeT caaAnnSource let r = rewriteToEdit df (annsA ps) rw+#else+ let r = rewriteToEdit df rw+#endif pure $ fromRight [] r instance ToTextEdit a => ToTextEdit [a] where@@ -134,6 +140,7 @@ data CodeActionArgs = CodeActionArgs { caaExportsMap :: IO ExportsMap,+ caaGhcSession :: IO (Maybe HscEnvEq), caaIdeOptions :: IO IdeOptions, caaParsedModule :: IO (Maybe ParsedModule), caaContents :: IO (Maybe T.Text),@@ -205,6 +212,7 @@ toCodeAction3 :: (ToCodeAction r) => (CodeActionArgs -> IO a) -> (a -> r) -> GhcideCodeAction toCodeAction3 get f = ReaderT $ \caa -> get caa >>= flip runReaderT caa . toCodeAction . f +-- | this instance returns a delta AST, useful for exactprint transforms instance ToCodeAction r => ToCodeAction (ParsedSource -> r) where toCodeAction f = ReaderT $ \caa@CodeActionArgs {caaAnnSource = x} -> x >>= \case@@ -267,3 +275,9 @@ instance ToCodeAction r => ToCodeAction (GlobalBindingTypeSigsResult -> r) where toCodeAction = toCodeAction2 caaGblSigs++instance ToCodeAction r => ToCodeAction (Maybe HscEnvEq -> r) where+ toCodeAction = toCodeAction1 caaGhcSession++instance ToCodeAction r => ToCodeAction (Maybe HscEnv -> r) where+ toCodeAction = toCodeAction1 ((fmap.fmap.fmap) hscEnv caaGhcSession)
src/Development/IDE/Plugin/CodeAction/ExactPrint.hs view
@@ -2,12 +2,16 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleInstances #-} module Development.IDE.Plugin.CodeAction.ExactPrint ( Rewrite (..), rewriteToEdit, rewriteToWEdit,+#if !MIN_VERSION_ghc(9,2,0) transferAnn,+#endif -- * Utilities appendConstraint,@@ -30,16 +34,28 @@ mapMaybe) import qualified Data.Text as T import Development.IDE.GHC.Compat-import qualified Development.IDE.GHC.Compat.Util as Util import Development.IDE.GHC.Error-import Development.IDE.GHC.ExactPrint (ASTElement (parseAST),- Annotate)+import Development.IDE.GHC.ExactPrint import Development.IDE.Spans.Common import GHC.Exts (IsList (fromList)) import Language.Haskell.GHC.ExactPrint+#if !MIN_VERSION_ghc(9,2,0)+import qualified Development.IDE.GHC.Compat.Util as Util import Language.Haskell.GHC.ExactPrint.Types (DeltaPos (DP), KeywordId (G), mkAnnKey)+#else+import Data.Default+import GHC (AddEpAnn (..), AnnContext (..), AnnParen (..),+ DeltaPos (SameLine), EpAnn (..), EpaLocation (EpaDelta),+ IsUnicodeSyntax (NormalSyntax),+ NameAdornment (NameParens), NameAnn (..), addAnns, ann, emptyComments,+ reAnnL, AnnList (..))+#endif import Language.LSP.Types+import Development.IDE.GHC.Util+import Data.Bifunctor (first)+import Control.Lens (_head, _last, over)+import GHC.Stack (HasCallStack) ------------------------------------------------------------------------------ @@ -47,35 +63,83 @@ -- given 'ast'. data Rewrite where Rewrite ::+#if !MIN_VERSION_ghc(9,2,0) Annotate ast =>+#else+ (ExactPrint (GenLocated (Anno ast) ast), ResetEntryDP (Anno ast), Outputable (GenLocated (Anno ast) ast), Data (GenLocated (Anno ast) ast)) =>+#endif -- | The 'SrcSpan' that we want to rewrite SrcSpan -> -- | The ast that we want to graft+#if !MIN_VERSION_ghc(9,2,0) (DynFlags -> TransformT (Either String) (Located ast)) ->+#else+ (DynFlags -> TransformT (Either String) (GenLocated (Anno ast) ast)) ->+#endif Rewrite ------------------------------------------------------------------------------+#if MIN_VERSION_ghc(9,2,0)+class ResetEntryDP ann where+ resetEntryDP :: GenLocated ann ast -> GenLocated ann ast+instance {-# OVERLAPPING #-} Default an => ResetEntryDP (SrcAnn an) where+ -- resetEntryDP = flip setEntryDP (SameLine 0)+ resetEntryDP (L srcAnn x) = setEntryDP (L srcAnn{ann=EpAnnNotUsed} x) (SameLine 0)+instance {-# OVERLAPPABLE #-} ResetEntryDP fallback where+ resetEntryDP = id+#endif -- | Convert a 'Rewrite' into a list of '[TextEdit]'.-rewriteToEdit ::+rewriteToEdit :: HasCallStack => DynFlags ->+#if !MIN_VERSION_ghc(9,2,0) Anns ->+#endif Rewrite -> Either String [TextEdit]-rewriteToEdit dflags anns (Rewrite dst f) = do- (ast, (anns, _), _) <- runTransformT anns $ do+rewriteToEdit dflags+#if !MIN_VERSION_ghc(9,2,0)+ anns+#endif+ (Rewrite dst f) = do+ (ast, anns , _) <- runTransformT+#if !MIN_VERSION_ghc(9,2,0)+ anns+#endif+ $ do ast <- f dflags+#if !MIN_VERSION_ghc(9,2,0) ast <$ setEntryDPT ast (DP (0, 0))+#else+ pure $ traceAst "REWRITE_result" $ resetEntryDP ast+#endif let editMap = [ TextEdit (fromJust $ srcSpanToRange dst) $- T.pack $ exactPrint ast anns+ T.pack $ exactPrint ast+#if !MIN_VERSION_ghc(9,2,0)+ (fst anns)+#endif ] pure editMap -- | Convert a 'Rewrite' into a 'WorkspaceEdit'-rewriteToWEdit :: DynFlags -> Uri -> Anns -> Rewrite -> Either String WorkspaceEdit-rewriteToWEdit dflags uri anns r = do- edits <- rewriteToEdit dflags anns r+rewriteToWEdit :: DynFlags+ -> Uri+#if !MIN_VERSION_ghc(9,2,0)+ -> Anns+#endif+ -> Rewrite+ -> Either String WorkspaceEdit+rewriteToWEdit dflags uri+#if !MIN_VERSION_ghc(9,2,0)+ anns+#endif+ r = do+ edits <- rewriteToEdit dflags+#if !MIN_VERSION_ghc(9,2,0)+ anns+#endif+ r return $ WorkspaceEdit { _changes = Just (fromList [(uri, List edits)])@@ -85,16 +149,20 @@ ------------------------------------------------------------------------------ +#if !MIN_VERSION_ghc(9,2,0) -- | Fix the parentheses around a type context fixParens ::- (Monad m, Data (HsType pass)) =>+ (Monad m, Data (HsType pass), pass ~ GhcPass p0) => Maybe DeltaPos -> Maybe DeltaPos -> LHsContext pass -> TransformT m [LHsType pass]-fixParens openDP closeDP ctxt@(L _ elems) = do+fixParens+ openDP closeDP+ ctxt@(L _ elems) = do -- Paren annotation for type contexts are usually quite screwed up -- we remove duplicates and fix negative DPs+ let parens = Map.fromList [(G AnnOpenP, dp00), (G AnnCloseP, dp00)] modifyAnnsT $ Map.adjust ( \x ->@@ -109,28 +177,44 @@ ) (mkAnnKey ctxt) return $ map dropHsParTy elems- where- parens = Map.fromList [(G AnnOpenP, dp00), (G AnnCloseP, dp00)]+#endif - dropHsParTy :: LHsType pass -> LHsType pass- dropHsParTy (L _ (HsParTy _ ty)) = ty- dropHsParTy other = other+dropHsParTy :: LHsType (GhcPass pass) -> LHsType (GhcPass pass)+dropHsParTy (L _ (HsParTy _ ty)) = ty+dropHsParTy other = other removeConstraint :: -- | Predicate: Which context to drop. (LHsType GhcPs -> Bool) -> LHsType GhcPs -> Rewrite-removeConstraint toRemove = go+removeConstraint toRemove = go . traceAst "REMOVE_CONSTRAINT_input" where- go (L l it@HsQualTy{hst_ctxt = L l' ctxt, hst_body}) = Rewrite l $ \_ -> do- let ctxt' = L l' $ filter (not . toRemove) ctxt- when ((toRemove <$> headMaybe ctxt) == Just True) $+ go :: LHsType GhcPs -> Rewrite+#if !MIN_VERSION_ghc(9,2,0)+ go (L l it@HsQualTy{hst_ctxt = L l' ctxt, hst_body}) = Rewrite (locA l) $ \_ -> do+#else+ go (L l it@HsQualTy{hst_ctxt = Just (L l' ctxt), hst_body}) = Rewrite (locA l) $ \_ -> do+#endif+ let ctxt' = filter (not . toRemove) ctxt+ removeStuff = (toRemove <$> headMaybe ctxt) == Just True+#if !MIN_VERSION_ghc(9,2,0)+ when removeStuff $ setEntryDPT hst_body (DP (0, 0))- return $ L l $ it{hst_ctxt = ctxt'}+ return $ L l $ it{hst_ctxt = L l' ctxt'}+#else+ let hst_body' = if removeStuff then resetEntryDP hst_body else hst_body+ return $ case ctxt' of+ [] -> hst_body'+ _ -> do+ let ctxt'' = over _last (first removeComma) ctxt'+ L l $ it{ hst_ctxt = Just $ L l' ctxt''+ , hst_body = hst_body'+ }+#endif go (L _ (HsParTy _ ty)) = go ty go (L _ HsForAllTy{hst_body}) = go hst_body- go (L l other) = Rewrite l $ \_ -> return $ L l other+ go (L l other) = Rewrite (locA l) $ \_ -> return $ L l other -- | Append a constraint at the end of a type context. -- If no context is present, a new one will be created.@@ -140,30 +224,47 @@ -- | The type signature where the constraint is to be inserted, also assuming annotated LHsType GhcPs -> Rewrite-appendConstraint constraintT = go+appendConstraint constraintT = go . traceAst "appendConstraint" where- go (L l it@HsQualTy{hst_ctxt = L l' ctxt}) = Rewrite l $ \df -> do+#if !MIN_VERSION_ghc(9,2,0)+ go (L l it@HsQualTy{hst_ctxt = L l' ctxt}) = Rewrite (locA l) $ \df -> do+#else+ go (L l it@HsQualTy{hst_ctxt = Just (L l' ctxt)}) = Rewrite (locA l) $ \df -> do+#endif constraint <- liftParseAST df constraintT+#if !MIN_VERSION_ghc(9,2,0) setEntryDPT constraint (DP (0, 1)) -- Paren annotations are usually attached to the first and last constraints, -- rather than to the constraint list itself, so to preserve them we need to reposition them closeParenDP <- lookupAnn (G AnnCloseP) `mapM` lastMaybe ctxt openParenDP <- lookupAnn (G AnnOpenP) `mapM` headMaybe ctxt- ctxt' <- fixParens (join openParenDP) (join closeParenDP) (L l' ctxt)-+ ctxt' <- fixParens+ (join openParenDP) (join closeParenDP)+ (L l' ctxt) addTrailingCommaT (last ctxt')- return $ L l $ it{hst_ctxt = L l' $ ctxt' ++ [constraint]}+#else+ constraint <- pure $ setEntryDP constraint (SameLine 1)+ let l'' = (fmap.fmap) (addParensToCtxt close_dp) l'+ -- For singleton constraints, the close Paren DP is attached to an HsPar wrapping the constraint+ -- we have to reposition it manually into the AnnContext+ close_dp = case ctxt of+ [L _ (HsParTy EpAnn{anns=AnnParen{ap_close}} _)] -> Just ap_close+ _ -> Nothing+ ctxt' = over _last (first addComma) $ map dropHsParTy ctxt+ return $ L l $ it{hst_ctxt = Just $ L l'' $ ctxt' ++ [constraint]}+#endif go (L _ HsForAllTy{hst_body}) = go hst_body go (L _ (HsParTy _ ty)) = go ty- go (L l other) = Rewrite l $ \df -> do+ go ast@(L l _) = Rewrite (locA l) $ \df -> do -- there isn't a context, so we must create one constraint <- liftParseAST df constraintT lContext <- uniqueSrcSpanT lTop <- uniqueSrcSpanT+#if !MIN_VERSION_ghc(9,2,0) let context = L lContext [constraint]- addSimpleAnnT context (DP (0, 0)) $+ addSimpleAnnT context dp00 $ (G AnnDarrow, DP (0, 1)) : concat [ [ (G AnnOpenP, dp00)@@ -171,14 +272,29 @@ ] | hsTypeNeedsParens sigPrec $ unLoc constraint ]- return $ L lTop $ HsQualTy noExtField context (L l other)+#else+ let context = Just $ reAnnL annCtxt emptyComments $ L lContext [resetEntryDP constraint]+ annCtxt = AnnContext (Just (NormalSyntax, epl 1)) [epl 0 | needsParens] [epl 0 | needsParens]+ needsParens = hsTypeNeedsParens sigPrec $ unLoc constraint+ ast <- pure $ setEntryDP ast (SameLine 1)+#endif -liftParseAST :: ASTElement ast => DynFlags -> String -> TransformT (Either String) (Located ast)+ return $ reLocA $ L lTop $ HsQualTy noExtField context ast++liftParseAST+ :: forall ast l. (ASTElement l ast, ExactPrint (LocatedAn l ast))+ => DynFlags -> String -> TransformT (Either String) (LocatedAn l ast) liftParseAST df s = case parseAST df "" s of+#if !MIN_VERSION_ghc(9,2,0) Right (anns, x) -> modifyAnnsT (anns <>) $> x+#else+ Right x -> pure (makeDeltaAst x)+#endif Left _ -> lift $ Left $ "No parse: " <> s -lookupAnn :: (Data a, Monad m) => KeywordId -> Located a -> TransformT m (Maybe DeltaPos)+#if !MIN_VERSION_ghc(9,2,0)+lookupAnn :: (Data a, Monad m)+ => KeywordId -> Located a -> TransformT m (Maybe DeltaPos) lookupAnn comment la = do anns <- getAnnsT return $ Map.lookup (mkAnnKey la) anns >>= lookup comment . annsDP@@ -186,6 +302,17 @@ dp00 :: DeltaPos dp00 = DP (0, 0) +-- | Copy anns attached to a into b with modification, then delete anns of a+transferAnn :: (Data a, Data b) => Located a -> Located b -> (Annotation -> Annotation) -> TransformT (Either String) ()+transferAnn la lb f = do+ anns <- getAnnsT+ let oldKey = mkAnnKey la+ newKey = mkAnnKey lb+ oldValue <- liftMaybe "Unable to find ann" $ Map.lookup oldKey anns+ putAnnsT $ Map.delete oldKey $ Map.insert newKey (f oldValue) anns++#endif+ headMaybe :: [a] -> Maybe a headMaybe [] = Nothing headMaybe (a : _) = Just a@@ -198,24 +325,15 @@ liftMaybe _ (Just x) = return x liftMaybe s _ = lift $ Left s --- | Copy anns attached to a into b with modification, then delete anns of a-transferAnn :: (Data a, Data b) => Located a -> Located b -> (Annotation -> Annotation) -> TransformT (Either String) ()-transferAnn la lb f = do- anns <- getAnnsT- let oldKey = mkAnnKey la- newKey = mkAnnKey lb- oldValue <- liftMaybe "Unable to find ann" $ Map.lookup oldKey anns- putAnnsT $ Map.delete oldKey $ Map.insert newKey (f oldValue) anns- ------------------------------------------------------------------------------ extendImport :: Maybe String -> String -> LImportDecl GhcPs -> Rewrite extendImport mparent identifier lDecl@(L l _) =- Rewrite l $ \df -> do+ Rewrite (locA l) $ \df -> do case mparent of Just parent -> extendImportViaParent df parent identifier lDecl _ -> extendImportTopLevel identifier lDecl --- | Add an identifier or a data type to import list+-- | Add an identifier or a data type to import list. Expects a Delta AST -- -- extendImportTopLevel "foo" AST: --@@ -232,19 +350,20 @@ , hasSibling <- not $ null lies = do src <- uniqueSrcSpanT top <- uniqueSrcSpanT- let rdr = L src $ mkRdrUnqual $ mkVarOcc thing-+ let rdr = reLocA $ L src $ mkRdrUnqual $ mkVarOcc thing let alreadyImported = showNameWithoutUniques (occName (unLoc rdr)) `elem` map (showNameWithoutUniques @OccName) (listify (const True) lies) when alreadyImported $ lift (Left $ thing <> " already imported") - let lie = L src $ IEName rdr- x = L top $ IEVar noExtField lie+ let lie = reLocA $ L src $ IEName rdr+ x = reLocA $ L top $ IEVar noExtField lie+ if x `elem` lies then lift (Left $ thing <> " already imported") else do+#if !MIN_VERSION_ghc(9,2,0) when hasSibling $ addTrailingCommaT (last lies) addSimpleAnnT x (DP (0, if hasSibling then 1 else 0)) []@@ -254,6 +373,14 @@ unless hasSibling $ transferAnn (L l' lies) (L l' [x]) id return $ L l it{ideclHiding = Just (hide, L l' $ lies ++ [x])}+#else++ x <- pure $ setEntryDP x (SameLine $ if hasSibling then 1 else 0)++ let fixLast = if hasSibling then first addComma else id+ lies' = over _last fixLast lies ++ [x]+ return $ L l it{ideclHiding = Just (hide, L l' lies')}+#endif extendImportTopLevel _ _ = lift $ Left "Unable to extend the import list" -- | Add an identifier with its parent to import list@@ -277,39 +404,58 @@ extendImportViaParent df parent child (L l it@ImportDecl{..}) | Just (hide, L l' lies) <- ideclHiding = go hide l' [] lies where- go :: Bool -> SrcSpan -> [LIE GhcPs] -> [LIE GhcPs] -> TransformT (Either String) (LImportDecl GhcPs) go _hide _l' _pre ((L _ll' (IEThingAll _ (L _ ie))) : _xs) | parent == unIEWrappedName ie = lift . Left $ child <> " already included in " <> parent <> " imports" go hide l' pre (lAbs@(L ll' (IEThingAbs _ absIE@(L _ ie))) : xs) -- ThingAbs ie => ThingWith ie child | parent == unIEWrappedName ie = do srcChild <- uniqueSrcSpanT- let childRdr = L srcChild $ mkRdrUnqual $ mkVarOcc child- childLIE = L srcChild $ IEName childRdr+ let childRdr = reLocA $ L srcChild $ mkRdrUnqual $ mkVarOcc child+ childLIE = reLocA $ L srcChild $ IEName childRdr+#if !MIN_VERSION_ghc(9,2,0) x :: LIE GhcPs = L ll' $ IEThingWith noExtField absIE NoIEWildcard [childLIE] [] -- take anns from ThingAbs, and attatch parens to it transferAnn lAbs x $ \old -> old{annsDP = annsDP old ++ [(G AnnOpenP, DP (0, 1)), (G AnnCloseP, dp00)]} addSimpleAnnT childRdr dp00 [(G AnnVal, dp00)]+#else+ x :: LIE GhcPs = L ll' $ IEThingWith (addAnns mempty [AddEpAnn AnnOpenP (EpaDelta (SameLine 1) []), AddEpAnn AnnCloseP def] emptyComments) absIE NoIEWildcard [childLIE]+#endif return $ L l it{ideclHiding = Just (hide, L l' $ reverse pre ++ [x] ++ xs)}+#if !MIN_VERSION_ghc(9,2,0) go hide l' pre ((L l'' (IEThingWith _ twIE@(L _ ie) _ lies' _)) : xs)+#else+ go hide l' pre ((L l'' (IEThingWith l''' twIE@(L _ ie) _ lies')) : xs)+#endif -- ThingWith ie lies' => ThingWith ie (lies' ++ [child]) | parent == unIEWrappedName ie- , hasSibling <- not $ null lies' =+ , hasSibling <- not $ null lies' = do srcChild <- uniqueSrcSpanT- let childRdr = L srcChild $ mkRdrUnqual $ mkVarOcc child-+ let childRdr = reLocA $ L srcChild $ mkRdrUnqual $ mkVarOcc child+#if MIN_VERSION_ghc(9,2,0)+ childRdr <- pure $ setEntryDP childRdr $ SameLine $ if hasSibling then 1 else 0+#endif let alreadyImported = showNameWithoutUniques (occName (unLoc childRdr)) `elem` map (showNameWithoutUniques @OccName) (listify (const True) lies') when alreadyImported $ lift (Left $ child <> " already included in " <> parent <> " imports") + let childLIE = reLocA $ L srcChild $ IEName childRdr+#if !MIN_VERSION_ghc(9,2,0) when hasSibling $ addTrailingCommaT (last lies')- let childLIE = L srcChild $ IEName childRdr addSimpleAnnT childRdr (DP (0, if hasSibling then 1 else 0)) [(G AnnVal, dp00)] return $ L l it{ideclHiding = Just (hide, L l' $ reverse pre ++ [L l'' (IEThingWith noExtField twIE NoIEWildcard (lies' ++ [childLIE]) [])] ++ xs)}+#else+ let it' = it{ideclHiding = Just (hide, lies)}+ lies = L l' $ reverse pre +++ [L l'' (IEThingWith l''' twIE NoIEWildcard (over _last fixLast lies' ++ [childLIE]))] ++ xs+ fixLast = if hasSibling then first addComma else id+ return $ if hasSibling+ then L l it'+ else L l it'+#endif go hide l' pre (x : xs) = go hide l' (x : pre) xs go hide l' pre [] | hasSibling <- not $ null pre = do@@ -318,13 +464,22 @@ srcParent <- uniqueSrcSpanT srcChild <- uniqueSrcSpanT parentRdr <- liftParseAST df parent- let childRdr = L srcChild $ mkRdrUnqual $ mkVarOcc child+ let childRdr = reLocA $ L srcChild $ mkRdrUnqual $ mkVarOcc child isParentOperator = hasParen parent+#if !MIN_VERSION_ghc(9,2,0) when hasSibling $ addTrailingCommaT (head pre)- let parentLIE = L srcParent $ (if isParentOperator then IEType else IEName) parentRdr- childLIE = L srcChild $ IEName childRdr- x :: LIE GhcPs = L l'' $ IEThingWith noExtField parentLIE NoIEWildcard [childLIE] []+ let parentLIE = L srcParent (if isParentOperator then IEType parentRdr else IEName parentRdr)+ childLIE = reLocA $ L srcChild $ IEName childRdr+#else+ let parentLIE = reLocA $ L srcParent $ (if isParentOperator then IEType (epl 0) parentRdr' else IEName parentRdr')+ parentRdr' = modifyAnns parentRdr $ \case+ it@NameAnn{nann_adornment = NameParens} -> it{nann_open = epl 1}+ other -> other+ childLIE = reLocA $ L srcChild $ IEName childRdr+#endif+#if !MIN_VERSION_ghc(9,2,0)+ x :: LIE GhcPs = reLocA $ L l'' $ IEThingWith noExtField parentLIE NoIEWildcard [childLIE] [] -- Add AnnType for the parent if it's parenthesized (type operator) when isParentOperator $ addSimpleAnnT parentLIE (DP (0, 0)) [(G AnnType, DP (0, 0))]@@ -335,6 +490,10 @@ -- we need change the ann key from `[]` to `:` to keep parens and other anns. unless hasSibling $ transferAnn (L l' $ reverse pre) (L l' [x]) id+#else+ x :: LIE GhcPs = reLocA $ L l'' $ IEThingWith listAnn parentLIE NoIEWildcard [childLIE]+ listAnn = epAnn srcParent [AddEpAnn AnnOpenP (epl 1), AddEpAnn AnnCloseP (epl 0)]+#endif return $ L l it{ideclHiding = Just (hide, L l' $ reverse pre ++ [x])} extendImportViaParent _ _ _ _ = lift $ Left "Unable to extend the import list via parent" @@ -345,6 +504,7 @@ hasParen ('(' : _) = True hasParen _ = False +#if !MIN_VERSION_ghc(9,2,0) unqalDP :: Int -> Bool -> [(KeywordId, DeltaPos)] unqalDP c paren = ( if paren@@ -352,6 +512,7 @@ else pure ) (G AnnVal, dp00)+#endif ------------------------------------------------------------------------------ @@ -360,28 +521,52 @@ String -> LImportDecl GhcPs -> Rewrite hideSymbol symbol lidecl@(L loc ImportDecl{..}) = case ideclHiding of- Nothing -> Rewrite loc $ extendHiding symbol lidecl Nothing- Just (True, hides) -> Rewrite loc $ extendHiding symbol lidecl (Just hides)- Just (False, imports) -> Rewrite loc $ deleteFromImport symbol lidecl imports+ Nothing -> Rewrite (locA loc) $ extendHiding symbol lidecl Nothing+ Just (True, hides) -> Rewrite (locA loc) $ extendHiding symbol lidecl (Just hides)+ Just (False, imports) -> Rewrite (locA loc) $ deleteFromImport symbol lidecl imports hideSymbol _ (L _ (XImportDecl _)) = error "cannot happen" extendHiding :: String -> LImportDecl GhcPs ->+#if !MIN_VERSION_ghc(9,2,0) Maybe (Located [LIE GhcPs]) ->+#else+ Maybe (XRec GhcPs [LIE GhcPs]) ->+#endif DynFlags -> TransformT (Either String) (LImportDecl GhcPs) extendHiding symbol (L l idecls) mlies df = do L l' lies <- case mlies of+#if !MIN_VERSION_ghc(9,2,0) Nothing -> flip L [] <$> uniqueSrcSpanT+#else+ Nothing -> do+ src <- uniqueSrcSpanT+ let ann = noAnnSrcSpanDP0 src+ ann' = flip (fmap.fmap) ann $ \x -> x+ {al_rest = [AddEpAnn AnnHiding (epl 1)]+ ,al_open = Just $ AddEpAnn AnnOpenP (epl 1)+ ,al_close = Just $ AddEpAnn AnnCloseP (epl 0)+ }+ return $ L ann' []+#endif Just pr -> pure pr let hasSibling = not $ null lies src <- uniqueSrcSpanT top <- uniqueSrcSpanT rdr <- liftParseAST df symbol- let lie = L src $ IEName rdr- x = L top $ IEVar noExtField lie+#if MIN_VERSION_ghc(9,2,0)+ rdr <- pure $ modifyAnns rdr $ addParens (isOperator $ unLoc rdr)+#endif+ let lie = reLocA $ L src $ IEName rdr+ x = reLocA $ L top $ IEVar noExtField lie+#if MIN_VERSION_ghc(9,2,0)+ x <- pure $ if hasSibling then first addComma x else x+ lies <- pure $ over _head (`setEntryDP` SameLine 1) lies+#endif+#if !MIN_VERSION_ghc(9,2,0) singleHide = L l' [x] when (isNothing mlies) $ do addSimpleAnnT@@ -394,13 +579,14 @@ addSimpleAnnT x (DP (0, 0)) [] addSimpleAnnT rdr dp00 $ unqalDP 0 $ isOperator $ unLoc rdr if hasSibling- then when hasSibling $ do+ then do addTrailingCommaT x addSimpleAnnT (head lies) (DP (0, 1)) [] unless (null $ tail lies) $ addTrailingCommaT (head lies) -- Why we need this? else forM_ mlies $ \lies0 -> do transferAnn lies0 singleHide id+#endif return $ L l idecls{ideclHiding = Just (True, L l' $ x : lies)} where isOperator = not . all isAlphaNum . occNameString . rdrNameOcc@@ -408,7 +594,11 @@ deleteFromImport :: String -> LImportDecl GhcPs ->+#if !MIN_VERSION_ghc(9,2,0) Located [LIE GhcPs] ->+#else+ XRec GhcPs [LIE GhcPs] ->+#endif DynFlags -> TransformT (Either String) (LImportDecl GhcPs) deleteFromImport (T.pack -> symbol) (L l idecl) llies@(L lieLoc lies) _ = do@@ -418,6 +608,7 @@ idecl { ideclHiding = Just (False, edited) }+#if !MIN_VERSION_ghc(9,2,0) -- avoid import A (foo,) whenJust (lastMaybe deletedLies) removeTrailingCommaT when (not (null lies) && null deletedLies) $ do@@ -428,9 +619,13 @@ [ (G AnnOpenP, DP (0, 1)) , (G AnnCloseP, DP (0, 0)) ]+#endif pure lidecl' where deletedLies =+#if MIN_VERSION_ghc(9,2,0)+ over _last removeTrailingComma $+#endif mapMaybe killLie lies killLie :: LIE GhcPs -> Maybe (LIE GhcPs) killLie v@(L _ (IEVar _ (L _ (unqualIEWrapName -> nam))))@@ -439,7 +634,11 @@ killLie v@(L _ (IEThingAbs _ (L _ (unqualIEWrapName -> nam)))) | nam == symbol = Nothing | otherwise = Just v+#if !MIN_VERSION_ghc(9,2,0) killLie (L lieL (IEThingWith xt ty@(L _ (unqualIEWrapName -> nam)) wild cons flds))+#else+ killLie (L lieL (IEThingWith xt ty@(L _ (unqualIEWrapName -> nam)) wild cons))+#endif | nam == symbol = Nothing | otherwise = Just $@@ -449,5 +648,7 @@ ty wild (filter ((/= symbol) . unqualIEWrapName . unLoc) cons)+#if !MIN_VERSION_ghc(9,2,0) (filter ((/= symbol) . T.pack . Util.unpackFS . flLabel . unLoc) flds)+#endif killLie v = Just v
src/Development/IDE/Plugin/Completions.hs view
@@ -1,14 +1,13 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} module Development.IDE.Plugin.Completions ( descriptor- , LocalCompletions(..)- , NonLocalCompletions(..) ) where import Control.Concurrent.Async (concurrently)-import Control.Concurrent.Extra+import Control.Concurrent.STM.Stats (readTVarIO) import Control.Monad.Extra import Control.Monad.IO.Class import Control.Monad.Trans.Maybe@@ -24,12 +23,9 @@ import Development.IDE.Core.Shake import Development.IDE.GHC.Compat import Development.IDE.GHC.Error (rangeToSrcSpan)-import Development.IDE.GHC.ExactPrint (Annotated (annsA),- GetAnnotatedParsedSource (GetAnnotatedParsedSource),- astA)+import Development.IDE.GHC.ExactPrint (GetAnnotatedParsedSource (GetAnnotatedParsedSource)) import Development.IDE.GHC.Util (prettyPrint) import Development.IDE.Graph-import Development.IDE.Graph.Classes import Development.IDE.Plugin.CodeAction (newImport, newImportToEdit) import Development.IDE.Plugin.CodeAction.ExactPrint@@ -41,7 +37,6 @@ import qualified Development.IDE.Types.KnownTargets as KT import Development.IDE.Types.Location import GHC.Exts (fromList, toList)-import GHC.Generics import Ide.Plugin.Config (Config) import Ide.Types import qualified Language.LSP.Server as LSP@@ -89,7 +84,7 @@ _ -> return ([], Nothing) -- Drop any explicit imports in ImportDecl if not hidden-dropListFromImportDecl :: GenLocated SrcSpan (ImportDecl GhcPs) -> GenLocated SrcSpan (ImportDecl GhcPs)+dropListFromImportDecl :: LImportDecl GhcPs -> LImportDecl GhcPs dropListFromImportDecl iDecl = let f d@ImportDecl {ideclHiding} = case ideclHiding of Just (False, _) -> d {ideclHiding=Nothing}@@ -98,20 +93,6 @@ f x = x in f <$> iDecl --- | Produce completions info for a file-type instance RuleResult LocalCompletions = CachedCompletions-type instance RuleResult NonLocalCompletions = CachedCompletions--data LocalCompletions = LocalCompletions- deriving (Eq, Show, Typeable, Generic)-instance Hashable LocalCompletions-instance NFData LocalCompletions--data NonLocalCompletions = NonLocalCompletions- deriving (Eq, Show, Typeable, Generic)-instance Hashable NonLocalCompletions-instance NFData NonLocalCompletions- -- | Generate code actions. getCompletionsLSP :: IdeState@@ -138,7 +119,7 @@ -- set up the exports map including both package and project-level identifiers packageExportsMapIO <- fmap(envPackageExports . fst) <$> useWithStaleFast GhcSession npath packageExportsMap <- mapM liftIO packageExportsMapIO- projectExportsMap <- liftIO $ readVar (exportsMap $ shakeExtras ide)+ projectExportsMap <- liftIO $ readTVarIO (exportsMap $ shakeExtras ide) let exportsMap = fromMaybe mempty packageExportsMap <> projectExportsMap let moduleExports = getModuleExportsMap exportsMap@@ -244,24 +225,31 @@ case existingImport of Just imp -> do fmap (nfp,) $ liftEither $- rewriteToWEdit df doc (annsA ps) $- extendImport (T.unpack <$> thingParent) (T.unpack newThing) imp+ rewriteToWEdit df doc+#if !MIN_VERSION_ghc(9,2,0)+ (annsA ps)+#endif+ $+ extendImport (T.unpack <$> thingParent) (T.unpack newThing) (makeDeltaAst imp) Nothing -> do let n = newImport importName sym importQual False sym = if isNothing importQual then Just it else Nothing it = case thingParent of Nothing -> newThing Just p -> p <> "(" <> newThing <> ")"- t <- liftMaybe $ snd <$> newImportToEdit n (astA ps) (fromMaybe "" contents)+ t <- liftMaybe $ snd <$> newImportToEdit+ n+ (astA ps)+ (fromMaybe "" contents) return (nfp, WorkspaceEdit {_changes=Just (fromList [(doc,List [t])]), _documentChanges=Nothing, _changeAnnotations=Nothing}) | otherwise = mzero -isWantedModule :: ModuleName -> Maybe ModuleName -> GenLocated l (ImportDecl pass) -> Bool+isWantedModule :: ModuleName -> Maybe ModuleName -> GenLocated l (ImportDecl GhcPs) -> Bool isWantedModule wantedModule Nothing (L _ it@ImportDecl{ideclName, ideclHiding = Just (False, _)}) = not (isQualifiedImport it) && unLoc ideclName == wantedModule isWantedModule wantedModule (Just qual) (L _ ImportDecl{ideclAs, ideclName, ideclHiding = Just (False, _)}) =- unLoc ideclName == wantedModule && (wantedModule == qual || (unLoc <$> ideclAs) == Just qual)+ unLoc ideclName == wantedModule && (wantedModule == qual || (unLoc . reLoc <$> ideclAs) == Just qual) isWantedModule _ _ _ = False liftMaybe :: Monad m => Maybe a -> MaybeT m a
src/Development/IDE/Plugin/Completions/Logic.hs view
@@ -20,7 +20,6 @@ import qualified Data.Map as Map import Data.Maybe (fromMaybe, isJust,- listToMaybe, mapMaybe) import qualified Data.Text as T import qualified Text.Fuzzy.Parallel as Fuzzy@@ -32,11 +31,13 @@ import Data.Functor import qualified Data.HashMap.Strict as HM import qualified Data.HashSet as HashSet+import Data.Monoid (First(..)) import Data.Ord (Down (Down)) import qualified Data.Set as Set import Development.IDE.Core.Compile import Development.IDE.Core.PositionMapping-import Development.IDE.GHC.Compat as GHC hiding (ppr)+import Development.IDE.GHC.Compat hiding (ppr)+import qualified Development.IDE.GHC.Compat as GHC import Development.IDE.GHC.Compat.Util import Development.IDE.GHC.Error import Development.IDE.GHC.Util@@ -47,6 +48,15 @@ import Development.IDE.Types.Exports import Development.IDE.Types.HscEnvEq import Development.IDE.Types.Options++#if MIN_VERSION_ghc(9,2,0)+import GHC.Plugins (Depth (AllTheWay),+ defaultSDocContext,+ mkUserStyle,+ neverQualify,+ renderWithContext,+ sdocStyle)+#endif import Ide.PluginUtils (mkLspCommand) import Ide.Types (CommandId (..), PluginId)@@ -80,11 +90,11 @@ -- i.e. where are the value decls and the type decls getCContext :: Position -> ParsedModule -> Maybe Context getCContext pos pm- | Just (L r modName) <- moduleHeader+ | Just (L (locA -> r) modName) <- moduleHeader , pos `isInsideSrcSpan` r = Just (ModuleContext (moduleNameString modName)) - | Just (L r _) <- exportList+ | Just (L (locA -> r) _) <- exportList , pos `isInsideSrcSpan` r = Just ExportContext @@ -103,23 +113,23 @@ imports = hsmodImports $ unLoc $ pm_parsed_source pm go :: LHsDecl GhcPs -> Maybe Context- go (L r SigD {})+ go (L (locA -> r) SigD {}) | pos `isInsideSrcSpan` r = Just TypeContext | otherwise = Nothing- go (L r GHC.ValD {})+ go (L (locA -> r) GHC.ValD {}) | pos `isInsideSrcSpan` r = Just ValueContext | otherwise = Nothing go _ = Nothing goInline :: GHC.LHsType GhcPs -> Maybe Context- goInline (GHC.L r _)+ goInline (GHC.L (locA -> r) _) | pos `isInsideSrcSpan` r = Just TypeContext goInline _ = Nothing importGo :: GHC.LImportDecl GhcPs -> Maybe Context- importGo (L r impDecl)+ importGo (L (locA -> r) impDecl) | pos `isInsideSrcSpan` r- = importInline importModuleName (ideclHiding impDecl)+ = importInline importModuleName (fmap (fmap reLoc) $ ideclHiding impDecl) <|> Just (ImportContext importModuleName) | otherwise = Nothing@@ -255,9 +265,9 @@ (TyVarTy _) -> noParensSnippet (LitTy _) -> noParensSnippet (TyConApp _ []) -> noParensSnippet- _ -> snippetText i ("(" <> showGhc t <> ")")+ _ -> snippetText i ("(" <> showForSnippet t <> ")") where- noParensSnippet = snippetText i (showGhc t)+ noParensSnippet = snippetText i (showForSnippet t) snippetText i t = "${" <> T.pack (show i) <> ":" <> t <> "}" getArgs :: Type -> [Type] getArgs t@@ -278,6 +288,16 @@ #endif | otherwise = [] ++showForSnippet :: Outputable a => a -> T.Text+#if MIN_VERSION_ghc(9,2,0)+showForSnippet x = T.pack $ renderWithContext ctxt $ GHC.ppr x -- FIXme+ where+ ctxt = defaultSDocContext{sdocStyle = mkUserStyle neverQualify AllTheWay}+#else+showForSnippet x = showGhc x+#endif+ mkModCompl :: T.Text -> CompletionItem mkModCompl label = CompletionItem label (Just CiModule) Nothing Nothing@@ -332,12 +352,12 @@ curModName = moduleName curMod curModNameText = ppr curModName - importMap = Map.fromList [ (l, imp) | imp@(L (RealSrcSpan l _) _) <- limports ]+ importMap = Map.fromList [ (l, imp) | imp@(L (locA -> (RealSrcSpan l _)) _) <- limports ] - iDeclToModName :: ImportDecl name -> ModuleName+ iDeclToModName :: ImportDecl GhcPs -> ModuleName iDeclToModName = unLoc . ideclName - asNamespace :: ImportDecl name -> ModuleName+ asNamespace :: ImportDecl GhcPs -> ModuleName asNamespace imp = maybe (iDeclToModName imp) GHC.unLoc (ideclAs imp) -- Full canonical names of imported modules importDeclerations = map unLoc limports@@ -383,7 +403,9 @@ let (mbParent, originName) = case par of NoParent -> (Nothing, nameOccName n) ParentIs n' -> (Just . T.pack $ printName n', nameOccName n)+#if !MIN_VERSION_ghc(9,2,0) FldParent n' lbl -> (Just . T.pack $ printName n', maybe (nameOccName n) mkVarOccFS lbl)+#endif tys <- catchSrcErrors (hsc_dflags packageState) "completion" $ do name' <- lookupName packageState m n return ( name' >>= safeTyThingType@@ -432,7 +454,7 @@ compls = concat [ case decl of SigD _ (TypeSig _ ids typ) ->- [mkComp id CiFunction (Just $ ppr typ) | id <- ids]+ [mkComp id CiFunction (Just $ showForSnippet typ) | id <- ids] ValD _ FunBind{fun_id} -> [ mkComp fun_id CiFunction Nothing | not (hasTypeSig fun_id)@@ -441,13 +463,13 @@ [mkComp id CiVariable Nothing | VarPat _ id <- listify (\(_ :: Pat GhcPs) -> True) pat_lhs] TyClD _ ClassDecl{tcdLName, tcdSigs} ->- mkComp tcdLName CiInterface (Just $ ppr tcdLName) :- [ mkComp id CiFunction (Just $ ppr typ)+ mkComp tcdLName CiInterface (Just $ showForSnippet tcdLName) :+ [ mkComp id CiFunction (Just $ showForSnippet typ) | L _ (ClassOpSig _ _ ids typ) <- tcdSigs , id <- ids] TyClD _ x ->- let generalCompls = [mkComp id cl (Just $ ppr $ tcdLName x)- | id <- listify (\(_ :: Located(IdP GhcPs)) -> True) x+ let generalCompls = [mkComp id cl (Just $ showForSnippet $ tyClDeclLName x)+ | id <- listify (\(_ :: LIdP GhcPs) -> True) x , let cl = occNameToComKind Nothing (rdrNameOcc $ unLoc id)] -- here we only have to look at the outermost type recordCompls = findRecordCompl uri pm (Local pos) x@@ -455,11 +477,11 @@ -- the constructors and snippets will be duplicated here giving the user 2 choices. generalCompls ++ recordCompls ForD _ ForeignImport{fd_name,fd_sig_ty} ->- [mkComp fd_name CiVariable (Just $ ppr fd_sig_ty)]+ [mkComp fd_name CiVariable (Just $ showForSnippet fd_sig_ty)] ForD _ ForeignExport{fd_name,fd_sig_ty} ->- [mkComp fd_name CiVariable (Just $ ppr fd_sig_ty)]+ [mkComp fd_name CiVariable (Just $ showForSnippet fd_sig_ty)] _ -> []- | L pos decl <- hsmodDecls,+ | L (locA -> pos) decl <- hsmodDecls, let mkComp = mkLocalComp pos ] @@ -470,8 +492,8 @@ -- to tell local completions and global completions apart -- instead of using the empty string here, we should probably introduce a new field... ensureTypeText = Just $ fromMaybe "" ty- pn = ppr n- doc = SpanDocText (getDocumentation [pm] n) (SpanDocUris Nothing Nothing)+ pn = showForSnippet n+ doc = SpanDocText (getDocumentation [pm] $ reLoc n) (SpanDocUris Nothing Nothing) findRecordCompl :: Uri -> ParsedModule -> Provenance -> TyClDecl GhcPs -> [CompItem] findRecordCompl uri pmod mn DataDecl {tcdLName, tcdDataDefn} = result@@ -480,24 +502,30 @@ (showGhc . unLoc $ con_name) field_labels mn doc Nothing | ConDeclH98{..} <- unLoc <$> dd_cons tcdDataDefn , Just con_details <- [getFlds con_args]- , let field_names = mapMaybe extract con_details- , let field_labels = showGhc . unLoc <$> field_names+ , let field_names = concatMap extract con_details+ , let field_labels = showGhc <$> field_names , (not . List.null) field_labels ]- doc = SpanDocText (getDocumentation [pmod] tcdLName) (SpanDocUris Nothing Nothing)+ doc = SpanDocText (getDocumentation [pmod] $ reLoc tcdLName) (SpanDocUris Nothing Nothing) - getFlds :: HsConDetails arg (Located [LConDeclField GhcPs]) -> Maybe [ConDeclField GhcPs] getFlds conArg = case conArg of RecCon rec -> Just $ unLoc <$> unLoc rec- PrefixCon _ -> Just []+ PrefixCon{} -> Just [] _ -> Nothing extract ConDeclField{..}- -- TODO: Why is cd_fld_names a list?- | Just fld_name <- rdrNameFieldOcc . unLoc <$> listToMaybe cd_fld_names = Just fld_name- | otherwise = Nothing+ -- NOTE: 'cd_fld_names' is grouped so that the fields+ -- sharing the same type declaration to fit in the same group; e.g.+ --+ -- @+ -- data Foo = Foo {arg1, arg2 :: Int, arg3 :: Int, arg4 :: Bool}+ -- @+ --+ -- is encoded as @[[arg1, arg2], [arg3], [arg4]]@+ -- Hence, we must concat nested arguments into one to get all the fields.+ = map (rdrNameFieldOcc . unLoc) cd_fld_names -- XConDeclField- extract _ = Nothing+ extract _ = [] findRecordCompl _ _ _ _ = [] ppr :: Outputable a => a -> T.Text@@ -596,8 +624,8 @@ where occ = nameOccName name ctyp = occNameToComKind Nothing occ- pn = ppr name- ty = ppr <$> typ+ pn = showForSnippet name+ ty = showForSnippet <$> typ thisModName = Local $ nameSrcSpan name compls = if T.null prefixModule@@ -681,14 +709,17 @@ importedFrom (provenance -> ImportedFrom m) = m importedFrom (provenance -> DefinedIn m) = m importedFrom (provenance -> Local _) = "local"+#if __GLASGOW_HASKELL__ < 810+ importedFrom _ = ""+#endif -- --------------------------------------------------------------------- -- helper functions for infix backticks -- --------------------------------------------------------------------- hasTrailingBacktick :: T.Text -> Position -> Bool-hasTrailingBacktick line Position { _character }- | T.length line > _character = (line `T.index` _character) == '`'+hasTrailingBacktick line Position { _character=(fromIntegral -> c) }+ | T.length line > c = (line `T.index` c) == '`' | otherwise = False isUsedAsInfix :: T.Text -> T.Text -> T.Text -> Position -> Maybe Backtick@@ -701,8 +732,8 @@ hasClosingBacktick = hasTrailingBacktick line pos openingBacktick :: T.Text -> T.Text -> T.Text -> Position -> Bool-openingBacktick line prefixModule prefixText Position { _character }- | backtickIndex < 0 || backtickIndex > T.length line = False+openingBacktick line prefixModule prefixText Position { _character=(fromIntegral -> c) }+ | backtickIndex < 0 || backtickIndex >= T.length line = False | otherwise = (line `T.index` backtickIndex) == '`' where backtickIndex :: Int@@ -714,7 +745,7 @@ else T.length prefixModule + 1 {- Because of "." -} in -- Points to the first letter of either the module or prefix text- _character - (prefixLength + moduleLength) - 1+ c - (prefixLength + moduleLength) - 1 -- ---------------------------------------------------------------------@@ -727,12 +758,8 @@ -} -- TODO: Turn this into an alex lexer that discards prefixes as if they were whitespace. stripPrefix :: T.Text -> T.Text-stripPrefix name = T.takeWhile (/=':') $ go prefixes- where- go [] = name- go (p:ps)- | T.isPrefixOf p name = T.drop (T.length p) name- | otherwise = go ps+stripPrefix name = T.takeWhile (/=':') $ fromMaybe name $+ getFirst $ foldMap (First . (`T.stripPrefix` name)) prefixes -- | Prefixes that can occur in a GHC OccName prefixes :: [T.Text]
src/Development/IDE/Plugin/Completions/Types.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE TypeFamilies #-} module Development.IDE.Plugin.Completions.Types ( module Development.IDE.Plugin.Completions.Types ) where@@ -11,8 +12,11 @@ import qualified Data.Text as T import Data.Aeson (FromJSON, ToJSON)+import Data.Hashable (Hashable) import Data.Text (Text)+import Data.Typeable (Typeable) import Development.IDE.GHC.Compat+import Development.IDE.Graph (RuleResult) import Development.IDE.Spans.Common import GHC.Generics (Generic) import Ide.Plugin.Config (Config)@@ -22,6 +26,20 @@ import Ide.Types (PluginId) import Language.LSP.Server (MonadLsp) import Language.LSP.Types (CompletionItemKind (..), Uri)++-- | Produce completions info for a file+type instance RuleResult LocalCompletions = CachedCompletions+type instance RuleResult NonLocalCompletions = CachedCompletions++data LocalCompletions = LocalCompletions+ deriving (Eq, Show, Typeable, Generic)+instance Hashable LocalCompletions+instance NFData LocalCompletions++data NonLocalCompletions = NonLocalCompletions+ deriving (Eq, Show, Typeable, Generic)+instance Hashable NonLocalCompletions+instance NFData NonLocalCompletions -- From haskell-ide-engine/src/Haskell/Ide/Engine/LSP/Completions.hs
src/Development/IDE/Plugin/Test.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE PackageImports #-} {-# LANGUAGE PolyKinds #-} -- | A plugin that adds custom messages for use in tests module Development.IDE.Plugin.Test@@ -12,7 +13,6 @@ ) where import Control.Concurrent (threadDelay)-import Control.Concurrent.Extra (readVar) import Control.Monad import Control.Monad.IO.Class import Control.Monad.STM@@ -46,6 +46,8 @@ import Ide.Types import qualified Language.LSP.Server as LSP import Language.LSP.Types+import qualified "list-t" ListT+import qualified StmContainers.Map as STM import System.Time.Extra type Age = Int@@ -124,7 +126,7 @@ res <- liftIO $ runAction "garbage collect dirty" s $ garbageCollectDirtyKeysOlderThan age parents return $ Right $ toJSON $ map show res testRequestHandler s GetStoredKeys = do- keys <- liftIO $ HM.keys <$> readVar (state $ shakeExtras s)+ keys <- liftIO $ atomically $ map fst <$> ListT.toList (STM.listT $ state $ shakeExtras s) return $ Right $ toJSON $ map show keys testRequestHandler s GetFilesOfInterest = do ff <- liftIO $ getFilesOfInterest s
src/Development/IDE/Plugin/TypeLenses.hs view
@@ -12,6 +12,7 @@ GlobalBindingTypeSigsResult (..), ) where +import Control.Concurrent.STM.Stats (atomically) import Control.DeepSeq (rwhnf) import Control.Monad (mzero) import Control.Monad.Extra (whenMaybe)@@ -96,12 +97,13 @@ mode <- usePropertyLsp #mode pId properties fmap (Right . List) $ case uriToFilePath' uri of Just (toNormalizedFilePath' -> filePath) -> liftIO $ do+ env <- fmap hscEnv <$> runAction "codeLens.GhcSession" ideState (use GhcSession filePath) tmr <- runAction "codeLens.TypeCheck" ideState (use TypeCheck filePath) bindings <- runAction "codeLens.GetBindings" ideState (use GetBindings filePath) gblSigs <- runAction "codeLens.GetGlobalBindingTypeSigs" ideState (use GetGlobalBindingTypeSigs filePath) - diag <- getDiagnostics ideState- hDiag <- getHiddenDiagnostics ideState+ diag <- atomically $ getDiagnostics ideState+ hDiag <- atomically $ getHiddenDiagnostics ideState let toWorkSpaceEdit tedit = WorkspaceEdit (Just $ Map.singleton uri $ List tedit) Nothing Nothing generateLensForGlobal sig@GlobalBindingTypeSig{..} = do@@ -122,9 +124,9 @@ case mode of Always -> pure (catMaybes $ generateLensForGlobal <$> gblSigs')- <> generateLensFromDiags (suggestLocalSignature False tmr bindings) -- we still need diagnostics for local bindings+ <> generateLensFromDiags (suggestLocalSignature False env tmr bindings) -- we still need diagnostics for local bindings Exported -> pure $ catMaybes $ generateLensForGlobal <$> filter gbExported gblSigs'- Diagnostics -> generateLensFromDiags $ suggestSignature False gblSigs tmr bindings+ Diagnostics -> generateLensFromDiags $ suggestSignature False env gblSigs tmr bindings Nothing -> pure [] generateLens :: PluginId -> Range -> T.Text -> WorkspaceEdit -> CodeLens@@ -139,9 +141,9 @@ -------------------------------------------------------------------------------- -suggestSignature :: Bool -> Maybe GlobalBindingTypeSigsResult -> Maybe TcModuleResult -> Maybe Bindings -> Diagnostic -> [(T.Text, [TextEdit])]-suggestSignature isQuickFix mGblSigs mTmr mBindings diag =- suggestGlobalSignature isQuickFix mGblSigs diag <> suggestLocalSignature isQuickFix mTmr mBindings diag+suggestSignature :: Bool -> Maybe HscEnv -> Maybe GlobalBindingTypeSigsResult -> Maybe TcModuleResult -> Maybe Bindings -> Diagnostic -> [(T.Text, [TextEdit])]+suggestSignature isQuickFix env mGblSigs mTmr mBindings diag =+ suggestGlobalSignature isQuickFix mGblSigs diag <> suggestLocalSignature isQuickFix env mTmr mBindings diag suggestGlobalSignature :: Bool -> Maybe GlobalBindingTypeSigsResult -> Diagnostic -> [(T.Text, [TextEdit])] suggestGlobalSignature isQuickFix mGblSigs Diagnostic{_message, _range}@@ -155,25 +157,26 @@ [(title, [action])] | otherwise = [] -suggestLocalSignature :: Bool -> Maybe TcModuleResult -> Maybe Bindings -> Diagnostic -> [(T.Text, [TextEdit])]-suggestLocalSignature isQuickFix mTmr mBindings Diagnostic{_message, _range = _range@Range{..}}+suggestLocalSignature :: Bool -> Maybe HscEnv -> Maybe TcModuleResult -> Maybe Bindings -> Diagnostic -> [(T.Text, [TextEdit])]+suggestLocalSignature isQuickFix mEnv mTmr mBindings Diagnostic{_message, _range = _range@Range{..}} | Just (_ :: T.Text, _ :: T.Text, _ :: T.Text, [identifier]) <- (T.unwords . T.words $ _message) =~~ ("Polymorphic local binding with no type signature: (.*) ::" :: T.Text) , Just bindings <- mBindings+ , Just env <- mEnv , localScope <- getFuzzyScope bindings _start _end , -- we can't use srcspan to lookup scoped bindings, because the error message reported by GHC includes the entire binding, instead of simply the name Just (name, ty) <- find (\(x, _) -> printName x == T.unpack identifier) localScope >>= \(name, mTy) -> (name,) <$> mTy , Just TcModuleResult{tmrTypechecked = TcGblEnv{tcg_rdr_env, tcg_sigs}} <- mTmr , -- not a top-level thing, to avoid duplication not $ name `elemNameSet` tcg_sigs- , tyMsg <- printSDocQualifiedUnsafe (mkPrintUnqualifiedDefault tcg_rdr_env) $ pprSigmaType ty+ , tyMsg <- printSDocQualifiedUnsafe (mkPrintUnqualifiedDefault env tcg_rdr_env) $ pprSigmaType ty , signature <- T.pack $ printName name <> " :: " <> tyMsg , startCharacter <- _character _start , startOfLine <- Position (_line _start) startCharacter , beforeLine <- Range startOfLine startOfLine , title <- if isQuickFix then "add signature: " <> signature else signature- , action <- TextEdit beforeLine $ signature <> "\n" <> T.replicate startCharacter " " =+ , action <- TextEdit beforeLine $ signature <> "\n" <> T.replicate (fromIntegral startCharacter) " " = [(title, [action])] | otherwise = [] @@ -212,7 +215,7 @@ -------------------------------------------------------------------------------- showDocRdrEnv :: HscEnv -> GlobalRdrEnv -> SDoc -> String-showDocRdrEnv env rdrEnv = showSDocForUser (hsc_dflags env) (mkPrintUnqualified (hsc_dflags env) rdrEnv)+showDocRdrEnv env rdrEnv = showSDocForUser' env (mkPrintUnqualifiedDefault env rdrEnv) data GetGlobalBindingTypeSigs = GetGlobalBindingTypeSigs deriving (Generic, Show, Eq, Ord, Hashable, NFData)
src/Development/IDE/Spans/AtPoint.hs view
@@ -1,8 +1,9 @@ -- Copyright (c) 2019 The DAML Authors. All rights reserved. -- SPDX-License-Identifier: Apache-2.0 -{-# LANGUAGE CPP #-}-{-# LANGUAGE GADTs #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE RankNTypes #-} -- | Gives information about symbols at a given point in DAML files. -- These are all pure functions that should execute quickly.@@ -51,6 +52,7 @@ import Data.List.Extra (dropEnd1, nubOrd) import Data.Version (showVersion)+import Development.IDE.Types.Shake (WithHieDb) import HieDb hiding (pointCommand) import System.Directory (doesFileExist) @@ -106,12 +108,12 @@ referencesAtPoint :: MonadIO m- => HieDb+ => WithHieDb -> NormalizedFilePath -- ^ The file the cursor is in -> Position -- ^ position in the file -> FOIReferences -- ^ references data for FOIs -> m [Location]-referencesAtPoint hiedb nfp pos refs = do+referencesAtPoint withHieDb nfp pos refs = do -- The database doesn't have up2date references data for the FOIs so we must collect those -- from the Shake graph. let (names, foiRefs, exclude) = foiReferencesAtPoint nfp pos refs@@ -121,12 +123,12 @@ Just mod -> do -- Look for references (strictly in project files, not dependencies), -- excluding the files in the FOIs (since those are in foiRefs)- rows <- liftIO $ findReferences hiedb True (nameOccName name) (Just $ moduleName mod) (Just $ moduleUnit mod) exclude+ rows <- liftIO $ withHieDb (\hieDb -> findReferences hieDb True (nameOccName name) (Just $ moduleName mod) (Just $ moduleUnit mod) exclude) pure $ mapMaybe rowToLoc rows typeRefs <- forM names $ \name -> case nameModule_maybe name of Just mod | isTcClsNameSpace (occNameSpace $ nameOccName name) -> do- refs <- liftIO $ findTypeRefs hiedb True (nameOccName name) (Just $ moduleName mod) (Just $ moduleUnit mod) exclude+ refs <- liftIO $ withHieDb (\hieDb -> findTypeRefs hieDb True (nameOccName name) (Just $ moduleName mod) (Just $ moduleUnit mod) exclude) pure $ mapMaybe typeRowToLoc refs _ -> pure [] pure $ nubOrd $ foiRefs ++ concat nonFOIRefs ++ concat typeRefs@@ -135,8 +137,8 @@ rowToLoc (row:.info) = flip Location range <$> mfile where range = Range start end- start = Position (refSLine row - 1) (refSCol row -1)- end = Position (refELine row - 1) (refECol row -1)+ start = Position (fromIntegral $ refSLine row - 1) (fromIntegral $ refSCol row -1)+ end = Position (fromIntegral $ refELine row - 1) (fromIntegral $ refECol row -1) mfile = case modInfoSrcFile info of Just f -> Just $ toUri f Nothing -> Nothing@@ -147,8 +149,8 @@ pure $ Location (toUri file) range where range = Range start end- start = Position (typeRefSLine row - 1) (typeRefSCol row -1)- end = Position (typeRefELine row - 1) (typeRefECol row -1)+ start = Position (fromIntegral $ typeRefSLine row - 1) (fromIntegral $ typeRefSCol row -1)+ end = Position (fromIntegral $ typeRefELine row - 1) (fromIntegral $ typeRefECol row -1) documentHighlight :: Monad m@@ -158,7 +160,13 @@ -> MaybeT m [DocumentHighlight] documentHighlight hf rf pos = pure highlights where- ns = concat $ pointCommand hf pos (rights . M.keys . getNodeIds)+#if MIN_VERSION_ghc(9,0,1)+ -- We don't want to show document highlights for evidence variables, which are supposed to be invisible+ notEvidence = not . any isEvidenceContext . identInfo+#else+ notEvidence = const True+#endif+ ns = concat $ pointCommand hf pos (rights . M.keys . M.filter notEvidence . getNodeIds) highlights = do n <- ns ref <- fromMaybe [] (M.lookup (Right n) rf)@@ -172,27 +180,27 @@ gotoTypeDefinition :: MonadIO m- => HieDb+ => WithHieDb -> LookupModule m -> IdeOptions -> HieAstResult -> Position -> MaybeT m [Location]-gotoTypeDefinition hiedb lookupModule ideOpts srcSpans pos- = lift $ typeLocationsAtPoint hiedb lookupModule ideOpts pos srcSpans+gotoTypeDefinition withHieDb lookupModule ideOpts srcSpans pos+ = lift $ typeLocationsAtPoint withHieDb lookupModule ideOpts pos srcSpans -- | Locate the definition of the name at a given position. gotoDefinition :: MonadIO m- => HieDb+ => WithHieDb -> LookupModule m -> IdeOptions -> M.Map ModuleName NormalizedFilePath -> HieASTs a -> Position -> MaybeT m [Location]-gotoDefinition hiedb getHieFile ideOpts imports srcSpans pos- = lift $ locationsAtPoint hiedb getHieFile ideOpts imports pos srcSpans+gotoDefinition withHieDb getHieFile ideOpts imports srcSpans pos+ = lift $ locationsAtPoint withHieDb getHieFile ideOpts imports pos srcSpans -- | Synopsis for the name at a given position. atPoint@@ -252,13 +260,13 @@ typeLocationsAtPoint :: forall m . MonadIO m- => HieDb+ => WithHieDb -> LookupModule m -> IdeOptions -> Position -> HieAstResult -> m [Location]-typeLocationsAtPoint hiedb lookupModule _ideOptions pos (HAR _ ast _ _ hieKind) =+typeLocationsAtPoint withHieDb lookupModule _ideOptions pos (HAR _ ast _ _ hieKind) = case hieKind of HieFromDisk hf -> let arr = hie_types hf@@ -283,12 +291,12 @@ HQualTy a b -> getTypes [a,b] HCastTy a -> getTypes [a] _ -> []- in fmap nubOrd $ concatMapM (fmap (fromMaybe []) . nameToLocation hiedb lookupModule) (getTypes ts)+ in fmap nubOrd $ concatMapM (fmap (fromMaybe []) . nameToLocation withHieDb lookupModule) (getTypes ts) HieFresh -> let ts = concat $ pointCommand ast pos getts getts x = nodeType ni ++ (mapMaybe identType $ M.elems $ nodeIdentifiers ni) where ni = nodeInfo x- in fmap nubOrd $ concatMapM (fmap (fromMaybe []) . nameToLocation hiedb lookupModule) (getTypes ts)+ in fmap nubOrd $ concatMapM (fmap (fromMaybe []) . nameToLocation withHieDb lookupModule) (getTypes ts) namesInType :: Type -> [Name] namesInType (TyVarTy n) = [varName n]@@ -306,23 +314,23 @@ locationsAtPoint :: forall m a . MonadIO m- => HieDb+ => WithHieDb -> LookupModule m -> IdeOptions -> M.Map ModuleName NormalizedFilePath -> Position -> HieASTs a -> m [Location]-locationsAtPoint hiedb lookupModule _ideOptions imports pos ast =+locationsAtPoint withHieDb lookupModule _ideOptions imports pos ast = let ns = concat $ pointCommand ast pos (M.keys . getNodeIds) zeroPos = Position 0 0 zeroRange = Range zeroPos zeroPos modToLocation m = fmap (\fs -> pure $ Location (fromNormalizedUri $ filePathToUri' fs) zeroRange) $ M.lookup m imports- in fmap (nubOrd . concat) $ mapMaybeM (either (pure . modToLocation) $ nameToLocation hiedb lookupModule) ns+ in fmap (nubOrd . concat) $ mapMaybeM (either (pure . modToLocation) $ nameToLocation withHieDb lookupModule) ns -- | Given a 'Name' attempt to find the location where it is defined.-nameToLocation :: MonadIO m => HieDb -> LookupModule m -> Name -> m (Maybe [Location])-nameToLocation hiedb lookupModule name = runMaybeT $+nameToLocation :: MonadIO m => WithHieDb -> LookupModule m -> Name -> m (Maybe [Location])+nameToLocation withHieDb lookupModule name = runMaybeT $ case nameSrcSpan name of sp@(RealSrcSpan rsp _) -- Lookup in the db if we got a location in a boot file@@ -344,14 +352,14 @@ -- In this case the interface files contain garbage source spans -- so we instead read the .hie files to get useful source spans. mod <- MaybeT $ return $ nameModule_maybe name- erow <- liftIO $ findDef hiedb (nameOccName name) (Just $ moduleName mod) (Just $ moduleUnit mod)+ erow <- liftIO $ withHieDb (\hieDb -> findDef hieDb (nameOccName name) (Just $ moduleName mod) (Just $ moduleUnit mod)) case erow of [] -> do -- If the lookup failed, try again without specifying a unit-id. -- This is a hack to make find definition work better with ghcide's nascent multi-component support, -- where names from a component that has been indexed in a previous session but not loaded in this -- session may end up with different unit ids- erow <- liftIO $ findDef hiedb (nameOccName name) (Just $ moduleName mod) Nothing+ erow <- liftIO $ withHieDb (\hieDb -> findDef hieDb (nameOccName name) (Just $ moduleName mod) Nothing) case erow of [] -> MaybeT $ pure Nothing xs -> lift $ mapMaybeM (runMaybeT . defRowToLocation lookupModule) xs@@ -359,8 +367,8 @@ defRowToLocation :: Monad m => LookupModule m -> Res DefRow -> MaybeT m Location defRowToLocation lookupModule (row:.info) = do- let start = Position (defSLine row - 1) (defSCol row - 1)- end = Position (defELine row - 1) (defECol row - 1)+ let start = Position (fromIntegral $ defSLine row - 1) (fromIntegral $ defSCol row - 1)+ end = Position (fromIntegral $ defELine row - 1) (fromIntegral $ defECol row - 1) range = Range start end file <- case modInfoSrcFile info of Just src -> pure $ toUri src@@ -382,8 +390,8 @@ loc = Location file range file = fromNormalizedUri . filePathToUri' . toNormalizedFilePath' $ srcFile range = Range start end- start = Position (defSLine - 1) (defSCol - 1)- end = Position (defELine - 1) (defECol - 1)+ start = Position (fromIntegral $ defSLine - 1) (fromIntegral $ defSCol - 1)+ end = Position (fromIntegral $ defELine - 1) (fromIntegral $ defECol - 1) defRowToSymbolInfo _ = Nothing pointCommand :: HieASTs t -> Position -> (HieAST t -> a) -> [a]@@ -403,7 +411,7 @@ Nothing -> Nothing Just ast' -> Just $ k ast' where- sloc fs = mkRealSrcLoc fs (line+1) (cha+1)+ sloc fs = mkRealSrcLoc fs (fromIntegral $ line+1) (fromIntegral $ cha+1) sp fs = mkRealSrcSpan (sloc fs) (sloc fs) line = _line pos cha = _character pos
src/Development/IDE/Spans/Documentation.hs view
@@ -1,213 +1,224 @@-{-# LANGUAGE RankNTypes #-}--- Copyright (c) 2019 The DAML Authors. All rights reserved.--- SPDX-License-Identifier: Apache-2.0--{-# LANGUAGE CPP #-}--module Development.IDE.Spans.Documentation (- getDocumentation- , getDocumentationTryGhc- , getDocumentationsTryGhc- , DocMap- , mkDocMap- ) where--import Control.Monad-import Control.Monad.IO.Class-import Control.Monad.Extra (findM)-import Data.Either-import Data.Foldable-import Data.List.Extra-import qualified Data.Map as M-import Data.Maybe-import qualified Data.Set as S-import qualified Data.Text as T-import Development.IDE.Core.Compile-import Development.IDE.Core.RuleTypes-import Development.IDE.GHC.Compat-import Development.IDE.GHC.Compat.Util-import Development.IDE.GHC.Error-import Development.IDE.Spans.Common-import System.Directory-import System.FilePath--import Language.LSP.Types (filePathToUri, getUri)--mkDocMap- :: HscEnv- -> RefMap a- -> TcGblEnv- -> IO DocAndKindMap-mkDocMap env rm this_mod =- do let (_ , DeclDocMap this_docs, _) = extractDocs this_mod- d <- foldrM getDocs (mkNameEnv $ M.toList $ fmap (`SpanDocString` SpanDocUris Nothing Nothing) this_docs) names- k <- foldrM getType (tcg_type_env this_mod) names- pure $ DKMap d k- where- getDocs n map- | maybe True (mod ==) $ nameModule_maybe n = pure map -- we already have the docs in this_docs, or they do not exist- | otherwise = do- doc <- getDocumentationTryGhc env mod n- pure $ extendNameEnv map n doc- getType n map- | isTcOcc $ occName n = do- kind <- lookupKind env mod n- pure $ maybe map (extendNameEnv map n) kind- | otherwise = pure map- names = rights $ S.toList idents- idents = M.keysSet rm- mod = tcg_mod this_mod--lookupKind :: HscEnv -> Module -> Name -> IO (Maybe TyThing)-lookupKind env mod =- fmap (fromRight Nothing) . catchSrcErrors (hsc_dflags env) "span" . lookupName env mod--getDocumentationTryGhc :: HscEnv -> Module -> Name -> IO SpanDoc-getDocumentationTryGhc env mod n = head <$> getDocumentationsTryGhc env mod [n]--getDocumentationsTryGhc :: HscEnv -> Module -> [Name] -> IO [SpanDoc]-getDocumentationsTryGhc env mod names = do- res <- catchSrcErrors (hsc_dflags env) "docs" $ getDocsBatch env mod names- case res of- Left _ -> return []- Right res -> zipWithM unwrap res names- where- unwrap (Right (Just docs, _)) n = SpanDocString docs <$> getUris n- unwrap _ n = mkSpanDocText n-- mkSpanDocText name =- SpanDocText [] <$> getUris name-- -- Get the uris to the documentation and source html pages if they exist- getUris name = do- (docFu, srcFu) <-- case nameModule_maybe name of- Just mod -> liftIO $ do- doc <- toFileUriText $ lookupDocHtmlForModule env mod- src <- toFileUriText $ lookupSrcHtmlForModule env mod- return (doc, src)- Nothing -> pure (Nothing, Nothing)- let docUri = (<> "#" <> selector <> showNameWithoutUniques name) <$> docFu- srcUri = (<> "#" <> showNameWithoutUniques name) <$> srcFu- selector- | isValName name = "v:"- | otherwise = "t:"- return $ SpanDocUris docUri srcUri-- toFileUriText = (fmap . fmap) (getUri . filePathToUri)--getDocumentation- :: HasSrcSpan name- => [ParsedModule] -- ^ All of the possible modules it could be defined in.- -> name -- ^ The name you want documentation for.- -> [T.Text]--- This finds any documentation between the name you want--- documentation for and the one before it. This is only an--- approximately correct algorithm and there are easily constructed--- cases where it will be wrong (if so then usually slightly but there--- may be edge cases where it is very wrong).--- TODO : Build a version of GHC exactprint to extract this information--- more accurately.-getDocumentation sources targetName = fromMaybe [] $ do- -- Find the module the target is defined in.- targetNameSpan <- realSpan $ getLoc targetName- tc <-- find ((==) (Just $ srcSpanFile targetNameSpan) . annotationFileName)- $ reverse sources -- TODO : Is reversing the list here really neccessary?-- -- Top level names bound by the module- let bs = [ n | let L _ HsModule{hsmodDecls} = pm_parsed_source tc- , L _ (ValD _ hsbind) <- hsmodDecls- , Just n <- [name_of_bind hsbind]- ]- -- Sort the names' source spans.- let sortedSpans = sortedNameSpans bs- -- Now go ahead and extract the docs.- let docs = ann tc- nameInd <- elemIndex targetNameSpan sortedSpans- let prevNameSpan =- if nameInd >= 1- then sortedSpans !! (nameInd - 1)- else zeroSpan $ srcSpanFile targetNameSpan- -- Annoyingly "-- |" documentation isn't annotated with a location,- -- so you have to pull it out from the elements.- pure- $ docHeaders- $ filter (\(L target _) -> isBetween target prevNameSpan targetNameSpan)- $ fold- docs- where- -- Get the name bound by a binding. We only concern ourselves with- -- @FunBind@ (which covers functions and variables).- name_of_bind :: HsBind GhcPs -> Maybe (Located RdrName)- name_of_bind FunBind {fun_id} = Just fun_id- name_of_bind _ = Nothing- -- Get source spans from names, discard unhelpful spans, remove- -- duplicates and sort.- sortedNameSpans :: [Located RdrName] -> [RealSrcSpan]- sortedNameSpans ls = nubSort (mapMaybe (realSpan . getLoc) ls)- isBetween target before after = before <= target && target <= after-#if MIN_VERSION_ghc(9,0,0)- ann = apiAnnComments . pm_annotations-#else- ann = fmap filterReal . snd . pm_annotations- filterReal :: [Located a] -> [RealLocated a]- filterReal = mapMaybe (\(L l v) -> (`L`v) <$> realSpan l)-#endif- annotationFileName :: ParsedModule -> Maybe FastString- annotationFileName = fmap srcSpanFile . listToMaybe . map getRealSrcSpan . fold . ann---- | Shows this part of the documentation-docHeaders :: [RealLocated AnnotationComment]- -> [T.Text]-docHeaders = mapMaybe (\(L _ x) -> wrk x)- where- wrk = \case- -- When `Opt_Haddock` is enabled.- AnnDocCommentNext s -> Just $ T.pack s- -- When `Opt_KeepRawTokenStream` enabled.- AnnLineComment s -> if "-- |" `isPrefixOf` s- then Just $ T.pack s- else Nothing- _ -> Nothing---- These are taken from haskell-ide-engine's Haddock plugin---- | Given a module finds the local @doc/html/Foo-Bar-Baz.html@ page.--- An example for a cabal installed module:--- @~/.cabal/store/ghc-8.10.1/vctr-0.12.1.2-98e2e861/share/doc/html/Data-Vector-Primitive.html@-lookupDocHtmlForModule :: HscEnv -> Module -> IO (Maybe FilePath)-lookupDocHtmlForModule =- lookupHtmlForModule (\pkgDocDir modDocName -> pkgDocDir </> modDocName <.> "html")---- | Given a module finds the hyperlinked source @doc/html/src/Foo.Bar.Baz.html@ page.--- An example for a cabal installed module:--- @~/.cabal/store/ghc-8.10.1/vctr-0.12.1.2-98e2e861/share/doc/html/src/Data.Vector.Primitive.html@-lookupSrcHtmlForModule :: HscEnv -> Module -> IO (Maybe FilePath)-lookupSrcHtmlForModule =- lookupHtmlForModule (\pkgDocDir modDocName -> pkgDocDir </> "src" </> modDocName <.> "html")--lookupHtmlForModule :: (FilePath -> FilePath -> FilePath) -> HscEnv -> Module -> IO (Maybe FilePath)-lookupHtmlForModule mkDocPath hscEnv m = do- -- try all directories- let mfs = fmap (concatMap go) (lookupHtmls hscEnv ui)- html <- findM doesFileExist (concat . maybeToList $ mfs)- -- canonicalize located html to remove /../ indirection which can break some clients- -- (vscode on Windows at least)- traverse canonicalizePath html- where- go pkgDocDir = map (mkDocPath pkgDocDir) mns- ui = moduleUnit m- -- try to locate html file from most to least specific name e.g.- -- first Language.LSP.Types.Uri.html and Language-Haskell-LSP-Types-Uri.html- -- then Language.LSP.Types.html and Language-Haskell-LSP-Types.html etc.- mns = do- chunks <- (reverse . drop1 . inits . splitOn ".") $ (moduleNameString . moduleName) m- -- The file might use "." or "-" as separator- map (`intercalate` chunks) [".", "-"]--lookupHtmls :: HscEnv -> Unit -> Maybe [FilePath]-lookupHtmls df ui =- -- use haddockInterfaces instead of haddockHTMLs: GHC treats haddockHTMLs as URL not path- -- and therefore doesn't expand $topdir on Windows- map takeDirectory . unitHaddockInterfaces <$> lookupUnit df ui+{-# LANGUAGE RankNTypes #-} +-- Copyright (c) 2019 The DAML Authors. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +{-# LANGUAGE CPP #-} + +module Development.IDE.Spans.Documentation ( + getDocumentation + , getDocumentationTryGhc + , getDocumentationsTryGhc + , DocMap + , mkDocMap + ) where + +import Control.Monad +import Control.Monad.Extra (findM) +import Control.Monad.IO.Class +import Data.Either +import Data.Foldable +import Data.List.Extra +import qualified Data.Map as M +import Data.Maybe +import qualified Data.Set as S +import qualified Data.Text as T +import Development.IDE.Core.Compile +import Development.IDE.Core.RuleTypes +import Development.IDE.GHC.Compat +import Development.IDE.GHC.Compat.Util +import Development.IDE.GHC.Error +import Development.IDE.Spans.Common +import System.Directory +import System.FilePath + +import Language.LSP.Types (filePathToUri, getUri) + +mkDocMap + :: HscEnv + -> RefMap a + -> TcGblEnv + -> IO DocAndKindMap +mkDocMap env rm this_mod = + do +#if MIN_VERSION_ghc(9,2,0) + (_ , DeclDocMap this_docs, _) <- extractDocs this_mod +#else + let (_ , DeclDocMap this_docs, _) = extractDocs this_mod +#endif + d <- foldrM getDocs (mkNameEnv $ M.toList $ fmap (`SpanDocString` SpanDocUris Nothing Nothing) this_docs) names + k <- foldrM getType (tcg_type_env this_mod) names + pure $ DKMap d k + where + getDocs n map + | maybe True (mod ==) $ nameModule_maybe n = pure map -- we already have the docs in this_docs, or they do not exist + | otherwise = do + doc <- getDocumentationTryGhc env mod n + pure $ extendNameEnv map n doc + getType n map + | isTcOcc $ occName n = do + kind <- lookupKind env mod n + pure $ maybe map (extendNameEnv map n) kind + | otherwise = pure map + names = rights $ S.toList idents + idents = M.keysSet rm + mod = tcg_mod this_mod + +lookupKind :: HscEnv -> Module -> Name -> IO (Maybe TyThing) +lookupKind env mod = + fmap (fromRight Nothing) . catchSrcErrors (hsc_dflags env) "span" . lookupName env mod + +getDocumentationTryGhc :: HscEnv -> Module -> Name -> IO SpanDoc +getDocumentationTryGhc env mod n = head <$> getDocumentationsTryGhc env mod [n] + +getDocumentationsTryGhc :: HscEnv -> Module -> [Name] -> IO [SpanDoc] +getDocumentationsTryGhc env mod names = do + res <- catchSrcErrors (hsc_dflags env) "docs" $ getDocsBatch env mod names + case res of + Left _ -> return [] + Right res -> zipWithM unwrap res names + where + unwrap (Right (Just docs, _)) n = SpanDocString docs <$> getUris n + unwrap _ n = mkSpanDocText n + + mkSpanDocText name = + SpanDocText [] <$> getUris name + + -- Get the uris to the documentation and source html pages if they exist + getUris name = do + (docFu, srcFu) <- + case nameModule_maybe name of + Just mod -> liftIO $ do + doc <- toFileUriText $ lookupDocHtmlForModule env mod + src <- toFileUriText $ lookupSrcHtmlForModule env mod + return (doc, src) + Nothing -> pure (Nothing, Nothing) + let docUri = (<> "#" <> selector <> showNameWithoutUniques name) <$> docFu + srcUri = (<> "#" <> showNameWithoutUniques name) <$> srcFu + selector + | isValName name = "v:" + | otherwise = "t:" + return $ SpanDocUris docUri srcUri + + toFileUriText = (fmap . fmap) (getUri . filePathToUri) + +getDocumentation + :: HasSrcSpan name + => [ParsedModule] -- ^ All of the possible modules it could be defined in. + -> name -- ^ The name you want documentation for. + -> [T.Text] +-- This finds any documentation between the name you want +-- documentation for and the one before it. This is only an +-- approximately correct algorithm and there are easily constructed +-- cases where it will be wrong (if so then usually slightly but there +-- may be edge cases where it is very wrong). +-- TODO : Build a version of GHC exactprint to extract this information +-- more accurately. +-- TODO : Implement this for GHC 9.2 with in-tree annotations +-- (alternatively, just remove it and rely soley on GHC's parsing) +getDocumentation sources targetName = fromMaybe [] $ do +#if MIN_VERSION_ghc(9,2,0) + Nothing +#else + -- Find the module the target is defined in. + targetNameSpan <- realSpan $ getLoc targetName + tc <- + find ((==) (Just $ srcSpanFile targetNameSpan) . annotationFileName) + $ reverse sources -- TODO : Is reversing the list here really neccessary? + + -- Top level names bound by the module + let bs = [ n | let L _ HsModule{hsmodDecls} = pm_parsed_source tc + , L _ (ValD _ hsbind) <- hsmodDecls + , Just n <- [name_of_bind hsbind] + ] + -- Sort the names' source spans. + let sortedSpans = sortedNameSpans bs + -- Now go ahead and extract the docs. + let docs = ann tc + nameInd <- elemIndex targetNameSpan sortedSpans + let prevNameSpan = + if nameInd >= 1 + then sortedSpans !! (nameInd - 1) + else zeroSpan $ srcSpanFile targetNameSpan + -- Annoyingly "-- |" documentation isn't annotated with a location, + -- so you have to pull it out from the elements. + pure + $ docHeaders + $ filter (\(L target _) -> isBetween target prevNameSpan targetNameSpan) + $ fold + docs + where + -- Get the name bound by a binding. We only concern ourselves with + -- @FunBind@ (which covers functions and variables). + name_of_bind :: HsBind GhcPs -> Maybe (Located RdrName) + name_of_bind FunBind {fun_id} = Just fun_id + name_of_bind _ = Nothing + -- Get source spans from names, discard unhelpful spans, remove + -- duplicates and sort. + sortedNameSpans :: [Located RdrName] -> [RealSrcSpan] + sortedNameSpans ls = nubSort (mapMaybe (realSpan . getLoc) ls) + isBetween target before after = before <= target && target <= after +#if MIN_VERSION_ghc(9,0,0) + ann = apiAnnComments . pm_annotations +#else + ann = fmap filterReal . snd . pm_annotations + filterReal :: [Located a] -> [RealLocated a] + filterReal = mapMaybe (\(L l v) -> (`L`v) <$> realSpan l) +#endif + annotationFileName :: ParsedModule -> Maybe FastString + annotationFileName = fmap srcSpanFile . listToMaybe . map getRealSrcSpan . fold . ann + +-- | Shows this part of the documentation +docHeaders :: [RealLocated AnnotationComment] + -> [T.Text] +docHeaders = mapMaybe (\(L _ x) -> wrk x) + where + wrk = \case + -- When `Opt_Haddock` is enabled. + AnnDocCommentNext s -> Just $ T.pack s + -- When `Opt_KeepRawTokenStream` enabled. + AnnLineComment s -> if "-- |" `isPrefixOf` s + then Just $ T.pack s + else Nothing + _ -> Nothing +#endif + +-- These are taken from haskell-ide-engine's Haddock plugin + +-- | Given a module finds the local @doc/html/Foo-Bar-Baz.html@ page. +-- An example for a cabal installed module: +-- @~/.cabal/store/ghc-8.10.1/vctr-0.12.1.2-98e2e861/share/doc/html/Data-Vector-Primitive.html@ +lookupDocHtmlForModule :: HscEnv -> Module -> IO (Maybe FilePath) +lookupDocHtmlForModule = + lookupHtmlForModule (\pkgDocDir modDocName -> pkgDocDir </> modDocName <.> "html") + +-- | Given a module finds the hyperlinked source @doc/html/src/Foo.Bar.Baz.html@ page. +-- An example for a cabal installed module: +-- @~/.cabal/store/ghc-8.10.1/vctr-0.12.1.2-98e2e861/share/doc/html/src/Data.Vector.Primitive.html@ +lookupSrcHtmlForModule :: HscEnv -> Module -> IO (Maybe FilePath) +lookupSrcHtmlForModule = + lookupHtmlForModule (\pkgDocDir modDocName -> pkgDocDir </> "src" </> modDocName <.> "html") + +lookupHtmlForModule :: (FilePath -> FilePath -> FilePath) -> HscEnv -> Module -> IO (Maybe FilePath) +lookupHtmlForModule mkDocPath hscEnv m = do + -- try all directories + let mfs = fmap (concatMap go) (lookupHtmls hscEnv ui) + html <- findM doesFileExist (concat . maybeToList $ mfs) + -- canonicalize located html to remove /../ indirection which can break some clients + -- (vscode on Windows at least) + traverse canonicalizePath html + where + go pkgDocDir = map (mkDocPath pkgDocDir) mns + ui = moduleUnit m + -- try to locate html file from most to least specific name e.g. + -- first Language.LSP.Types.Uri.html and Language-Haskell-LSP-Types-Uri.html + -- then Language.LSP.Types.html and Language-Haskell-LSP-Types.html etc. + mns = do + chunks <- (reverse . drop1 . inits . splitOn ".") $ (moduleNameString . moduleName) m + -- The file might use "." or "-" as separator + map (`intercalate` chunks) [".", "-"] + +lookupHtmls :: HscEnv -> Unit -> Maybe [FilePath] +lookupHtmls df ui = + -- use haddockInterfaces instead of haddockHTMLs: GHC treats haddockHTMLs as URL not path + -- and therefore doesn't expand $topdir on Windows + map takeDirectory . unitHaddockInterfaces <$> lookupUnit df ui
src/Development/IDE/Spans/LocalBindings.hs view
@@ -1,140 +1,140 @@-{-# LANGUAGE DerivingStrategies #-}--module Development.IDE.Spans.LocalBindings- ( Bindings- , getLocalScope- , getFuzzyScope- , getDefiningBindings- , getFuzzyDefiningBindings- , bindings- ) where--import Control.DeepSeq-import Control.Monad-import Data.Bifunctor-import Data.IntervalMap.FingerTree (Interval (..), IntervalMap)-import qualified Data.IntervalMap.FingerTree as IM-import qualified Data.List as L-import qualified Data.Map as M-import qualified Data.Set as S-import Development.IDE.GHC.Compat (Name, RefMap, Scope (..), Type,- getBindSiteFromContext,- getScopeFromContext, identInfo,- identType, NameEnv, nameEnvElts,- unitNameEnv, isSystemName,- RealSrcSpan, realSrcSpanStart,- realSrcSpanEnd)--import Development.IDE.GHC.Error-import Development.IDE.Types.Location----------------------------------------------------------------------------------- | Turn a 'RealSrcSpan' into an 'Interval'.-realSrcSpanToInterval :: RealSrcSpan -> Interval Position-realSrcSpanToInterval rss =- Interval- (realSrcLocToPosition $ realSrcSpanStart rss)- (realSrcLocToPosition $ realSrcSpanEnd rss)--bindings :: RefMap Type -> Bindings-bindings = uncurry Bindings . localBindings----------------------------------------------------------------------------------- | Compute which identifiers are in scope at every point in the AST. Use--- 'getLocalScope' to find the results.-localBindings- :: RefMap Type- -> ( IntervalMap Position (NameEnv (Name, Maybe Type))- , IntervalMap Position (NameEnv (Name, Maybe Type))- )-localBindings refmap = bimap mk mk $ unzip $ do- (ident, refs) <- M.toList refmap- Right name <- pure ident- (_, ident_details) <- refs- let ty = identType ident_details- info <- S.toList $ identInfo ident_details- pure- ( do- Just scopes <- pure $ getScopeFromContext info- scope <- scopes >>= \case- LocalScope scope -> pure $ realSrcSpanToInterval scope- _ -> []- pure ( scope- , unitNameEnv name (name,ty)- )- , do- Just scope <- pure $ getBindSiteFromContext info- pure ( realSrcSpanToInterval scope- , unitNameEnv name (name,ty)- )- )- where- mk = L.foldl' (flip (uncurry IM.insert)) mempty . join----------------------------------------------------------------------------------- | The available bindings at every point in a Haskell tree.-data Bindings = Bindings- { getLocalBindings- :: IntervalMap Position (NameEnv (Name, Maybe Type))- , getBindingSites- :: IntervalMap Position (NameEnv (Name, Maybe Type))- }--instance Semigroup Bindings where- Bindings a1 b1 <> Bindings a2 b2- = Bindings (a1 <> a2) (b1 <> b2)--instance Monoid Bindings where- mempty = Bindings mempty mempty--instance NFData Bindings where- rnf = rwhnf--instance Show Bindings where- show _ = "<bindings>"------------------------------------------------------------------------------------ | Given a 'Bindings' get every identifier in scope at the given--- 'RealSrcSpan',-getLocalScope :: Bindings -> RealSrcSpan -> [(Name, Maybe Type)]-getLocalScope bs rss- = nameEnvElts- $ foldMap snd- $ IM.dominators (realSrcSpanToInterval rss)- $ getLocalBindings bs----------------------------------------------------------------------------------- | Given a 'Bindings', get every binding currently active at a given--- 'RealSrcSpan',-getDefiningBindings :: Bindings -> RealSrcSpan -> [(Name, Maybe Type)]-getDefiningBindings bs rss- = nameEnvElts- $ foldMap snd- $ IM.dominators (realSrcSpanToInterval rss)- $ getBindingSites bs----- | Lookup all names in scope in any span that intersects the interval--- defined by the two positions.--- This is meant for use with the fuzzy `PositionRange` returned by `PositionMapping`-getFuzzyScope :: Bindings -> Position -> Position -> [(Name, Maybe Type)]-getFuzzyScope bs a b- = filter (not . isSystemName . fst)- $ nameEnvElts- $ foldMap snd- $ IM.intersections (Interval a b)- $ getLocalBindings bs----------------------------------------------------------------------------------- | Given a 'Bindings', get every binding that intersects the interval defined--- by the two positions.--- This is meant for use with the fuzzy `PositionRange` returned by--- `PositionMapping`-getFuzzyDefiningBindings :: Bindings -> Position -> Position -> [(Name, Maybe Type)]-getFuzzyDefiningBindings bs a b- = nameEnvElts- $ foldMap snd- $ IM.intersections (Interval a b)- $ getBindingSites bs-+{-# LANGUAGE DerivingStrategies #-} + +module Development.IDE.Spans.LocalBindings + ( Bindings + , getLocalScope + , getFuzzyScope + , getDefiningBindings + , getFuzzyDefiningBindings + , bindings + ) where + +import Control.DeepSeq +import Control.Monad +import Data.Bifunctor +import Data.IntervalMap.FingerTree (Interval (..), IntervalMap) +import qualified Data.IntervalMap.FingerTree as IM +import qualified Data.List as L +import qualified Data.Map as M +import qualified Data.Set as S +import Development.IDE.GHC.Compat (Name, NameEnv, RealSrcSpan, + RefMap, Scope (..), Type, + getBindSiteFromContext, + getScopeFromContext, identInfo, + identType, isSystemName, + nameEnvElts, realSrcSpanEnd, + realSrcSpanStart, unitNameEnv) + +import Development.IDE.GHC.Error +import Development.IDE.Types.Location + +------------------------------------------------------------------------------ +-- | Turn a 'RealSrcSpan' into an 'Interval'. +realSrcSpanToInterval :: RealSrcSpan -> Interval Position +realSrcSpanToInterval rss = + Interval + (realSrcLocToPosition $ realSrcSpanStart rss) + (realSrcLocToPosition $ realSrcSpanEnd rss) + +bindings :: RefMap Type -> Bindings +bindings = uncurry Bindings . localBindings + +------------------------------------------------------------------------------ +-- | Compute which identifiers are in scope at every point in the AST. Use +-- 'getLocalScope' to find the results. +localBindings + :: RefMap Type + -> ( IntervalMap Position (NameEnv (Name, Maybe Type)) + , IntervalMap Position (NameEnv (Name, Maybe Type)) + ) +localBindings refmap = bimap mk mk $ unzip $ do + (ident, refs) <- M.toList refmap + Right name <- pure ident + (_, ident_details) <- refs + let ty = identType ident_details + info <- S.toList $ identInfo ident_details + pure + ( do + Just scopes <- pure $ getScopeFromContext info + scope <- scopes >>= \case + LocalScope scope -> pure $ realSrcSpanToInterval scope + _ -> [] + pure ( scope + , unitNameEnv name (name,ty) + ) + , do + Just scope <- pure $ getBindSiteFromContext info + pure ( realSrcSpanToInterval scope + , unitNameEnv name (name,ty) + ) + ) + where + mk = L.foldl' (flip (uncurry IM.insert)) mempty . join + +------------------------------------------------------------------------------ +-- | The available bindings at every point in a Haskell tree. +data Bindings = Bindings + { getLocalBindings + :: IntervalMap Position (NameEnv (Name, Maybe Type)) + , getBindingSites + :: IntervalMap Position (NameEnv (Name, Maybe Type)) + } + +instance Semigroup Bindings where + Bindings a1 b1 <> Bindings a2 b2 + = Bindings (a1 <> a2) (b1 <> b2) + +instance Monoid Bindings where + mempty = Bindings mempty mempty + +instance NFData Bindings where + rnf = rwhnf + +instance Show Bindings where + show _ = "<bindings>" + + +------------------------------------------------------------------------------ +-- | Given a 'Bindings' get every identifier in scope at the given +-- 'RealSrcSpan', +getLocalScope :: Bindings -> RealSrcSpan -> [(Name, Maybe Type)] +getLocalScope bs rss + = nameEnvElts + $ foldMap snd + $ IM.dominators (realSrcSpanToInterval rss) + $ getLocalBindings bs + +------------------------------------------------------------------------------ +-- | Given a 'Bindings', get every binding currently active at a given +-- 'RealSrcSpan', +getDefiningBindings :: Bindings -> RealSrcSpan -> [(Name, Maybe Type)] +getDefiningBindings bs rss + = nameEnvElts + $ foldMap snd + $ IM.dominators (realSrcSpanToInterval rss) + $ getBindingSites bs + + +-- | Lookup all names in scope in any span that intersects the interval +-- defined by the two positions. +-- This is meant for use with the fuzzy `PositionRange` returned by `PositionMapping` +getFuzzyScope :: Bindings -> Position -> Position -> [(Name, Maybe Type)] +getFuzzyScope bs a b + = filter (not . isSystemName . fst) + $ nameEnvElts + $ foldMap snd + $ IM.intersections (Interval a b) + $ getLocalBindings bs + +------------------------------------------------------------------------------ +-- | Given a 'Bindings', get every binding that intersects the interval defined +-- by the two positions. +-- This is meant for use with the fuzzy `PositionRange` returned by +-- `PositionMapping` +getFuzzyDefiningBindings :: Bindings -> Position -> Position -> [(Name, Maybe Type)] +getFuzzyDefiningBindings bs a b + = nameEnvElts + $ foldMap snd + $ IM.intersections (Interval a b) + $ getBindingSites bs +
+ src/Development/IDE/Spans/Pragmas.hs view
@@ -0,0 +1,432 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE MultiWayIf #-}++module Development.IDE.Spans.Pragmas+ ( NextPragmaInfo(..)+ , LineSplitTextEdits(..)+ , getNextPragmaInfo ) where++import Data.Bits (Bits (setBit))+import Data.Function ((&))+import qualified Data.List as List+import qualified Data.Maybe as Maybe+import Data.Text (Text)+import qualified Data.Text as Text+import Development.IDE (srcSpanToRange)+import Development.IDE.GHC.Compat+import Development.IDE.GHC.Compat.Util+import qualified Language.LSP.Types as LSP++getNextPragmaInfo :: DynFlags -> Maybe Text -> NextPragmaInfo+getNextPragmaInfo dynFlags sourceText =+ if | Just sourceText <- sourceText+ , let sourceStringBuffer = stringToStringBuffer (Text.unpack sourceText)+ , POk _ parserState <- parsePreDecl dynFlags sourceStringBuffer+ -> case parserState of+ ParserStateNotDone{ nextPragma } -> nextPragma+ ParserStateDone{ nextPragma } -> nextPragma+ | otherwise+ -> NextPragmaInfo 0 Nothing++-- Pre-declaration comments parser -----------------------------------------------------++-- | Each mode represents the "strongest" thing we've seen so far.+-- From strongest to weakest:+-- ModePragma, ModeHaddock, ModeComment, ModeInitial+data Mode = ModePragma | ModeHaddock | ModeComment | ModeInitial deriving Show++data LineSplitTextEdits = LineSplitTextEdits {+ lineSplitInsertTextEdit :: !LSP.TextEdit,+ lineSplitDeleteTextEdit :: !LSP.TextEdit+} deriving Show++data NextPragmaInfo = NextPragmaInfo {+ nextPragmaLine :: !Int,+ lineSplitTextEdits :: !(Maybe LineSplitTextEdits)+} deriving Show++data ParserState+ = ParserStateNotDone+ { nextPragma :: !NextPragmaInfo+ , mode :: !Mode+ , lastBlockCommentLine :: !Int+ , lastPragmaLine :: !Int+ , isLastTokenHash :: !Bool+ }+ | ParserStateDone { nextPragma :: NextPragmaInfo }+ deriving Show++isPragma :: String -> Bool+isPragma = List.isPrefixOf "{-#"++isDownwardBlockHaddock :: String -> Bool+isDownwardBlockHaddock = List.isPrefixOf "{-|"++isDownwardLineHaddock :: String -> Bool+isDownwardLineHaddock = List.isPrefixOf "-- |"++-- need to merge tokens that are deleted/inserted into one TextEdit each+-- to work around some weird TextEdits applied in reversed order issue+updateLineSplitTextEdits :: LSP.Range -> String -> Maybe LineSplitTextEdits -> LineSplitTextEdits+updateLineSplitTextEdits tokenRange tokenString prevLineSplitTextEdits+ | Just prevLineSplitTextEdits <- prevLineSplitTextEdits+ , let LineSplitTextEdits+ { lineSplitInsertTextEdit = prevInsertTextEdit+ , lineSplitDeleteTextEdit = prevDeleteTextEdit } = prevLineSplitTextEdits+ , let LSP.TextEdit prevInsertRange prevInsertText = prevInsertTextEdit+ , let LSP.TextEdit prevDeleteRange _prevDeleteText = prevDeleteTextEdit+ , let LSP.Range prevInsertStartPos prevInsertEndPos = prevInsertRange+ , let LSP.Position _prevInsertStartLine _prevInsertStartCol = prevInsertStartPos+ , let LSP.Position _prevInsertEndLine _prevInsertEndCol = prevInsertEndPos+ , let LSP.Range prevDeleteStartPos prevDeleteEndPos = prevDeleteRange+ , let LSP.Position _prevDeleteStartLine _prevDeleteStartCol = prevDeleteStartPos+ , let LSP.Position _prevDeleteEndLine prevDeleteEndCol = prevDeleteEndPos+ , let currInsertRange = prevInsertRange+ , let currInsertText =+ Text.init prevInsertText+ <> Text.replicate (fromIntegral $ startCol - prevDeleteEndCol) " "+ <> Text.pack (List.take newLineCol tokenString)+ <> "\n"+ , let currInsertTextEdit = LSP.TextEdit currInsertRange currInsertText+ , let currDeleteStartPos = prevDeleteStartPos+ , let currDeleteEndPos = LSP.Position endLine endCol+ , let currDeleteRange = LSP.Range currDeleteStartPos currDeleteEndPos+ , let currDeleteTextEdit = LSP.TextEdit currDeleteRange ""+ = LineSplitTextEdits currInsertTextEdit currDeleteTextEdit+ | otherwise+ , let LSP.Range startPos _ = tokenRange+ , let deleteTextEdit = LSP.TextEdit (LSP.Range startPos startPos{ LSP._character = startCol + fromIntegral newLineCol }) ""+ , let insertPosition = LSP.Position (startLine + 1) 0+ , let insertRange = LSP.Range insertPosition insertPosition+ , let insertText = Text.pack (List.take newLineCol tokenString) <> "\n"+ , let insertTextEdit = LSP.TextEdit insertRange insertText+ = LineSplitTextEdits insertTextEdit deleteTextEdit+ where+ LSP.Range (LSP.Position startLine startCol) (LSP.Position endLine endCol) = tokenRange++ newLineCol = Maybe.fromMaybe (length tokenString) (List.elemIndex '\n' tokenString)++-- ITvarsym "#" after a block comment is a parse error so we don't need to worry about it+updateParserState :: Token -> LSP.Range -> ParserState -> ParserState+updateParserState token range prevParserState+ | ParserStateNotDone+ { nextPragma = prevNextPragma@NextPragmaInfo{ lineSplitTextEdits = prevLineSplitTextEdits }+ , mode = prevMode+ , lastBlockCommentLine+ , lastPragmaLine+ } <- prevParserState+ , let defaultParserState = prevParserState { isLastTokenHash = False }+ , let LSP.Range (LSP.Position (fromIntegral -> startLine) _) (LSP.Position (fromIntegral -> endLine) _) = range+ = case prevMode of+ ModeInitial ->+ case token of+ ITvarsym "#" -> defaultParserState{ isLastTokenHash = True }+#if !MIN_VERSION_ghc(9,2,0)+ ITlineComment s+#else+ ITlineComment s _+#endif+ | isDownwardLineHaddock s -> defaultParserState{ mode = ModeHaddock }+ | otherwise ->+ defaultParserState+ { nextPragma = NextPragmaInfo (endLine + 1) Nothing+ , mode = ModeComment }+#if !MIN_VERSION_ghc(9,2,0)+ ITblockComment s+#else+ ITblockComment s _+#endif+ | isPragma s ->+ defaultParserState+ { nextPragma = NextPragmaInfo (endLine + 1) Nothing+ , mode = ModePragma+ , lastPragmaLine = endLine }+ | isDownwardBlockHaddock s -> defaultParserState{ mode = ModeHaddock }+ | otherwise ->+ defaultParserState+ { nextPragma = NextPragmaInfo (endLine + 1) Nothing+ , mode = ModeComment+ , lastBlockCommentLine = endLine }+ _ -> ParserStateDone prevNextPragma+ ModeComment ->+ case token of+ ITvarsym "#" -> defaultParserState{ isLastTokenHash = True }+#if !MIN_VERSION_ghc(9,2,0)+ ITlineComment s+#else+ ITlineComment s _+#endif+ | hasDeleteStartedOnSameLine startLine prevLineSplitTextEdits+ , let currLineSplitTextEdits = updateLineSplitTextEdits range s prevLineSplitTextEdits ->+ defaultParserState{ nextPragma = prevNextPragma{ lineSplitTextEdits = Just currLineSplitTextEdits } }+ | isDownwardLineHaddock s+ , lastBlockCommentLine == startLine+ , let currLineSplitTextEdits = updateLineSplitTextEdits range s Nothing ->+ defaultParserState+ { nextPragma = prevNextPragma{ lineSplitTextEdits = Just currLineSplitTextEdits }+ , mode = ModeHaddock }+ | otherwise ->+ defaultParserState { nextPragma = NextPragmaInfo (endLine + 1) Nothing }+#if !MIN_VERSION_ghc(9,2,0)+ ITblockComment s+#else+ ITblockComment s _+#endif+ | isPragma s ->+ defaultParserState+ { nextPragma = NextPragmaInfo (endLine + 1) Nothing+ , mode = ModePragma+ , lastPragmaLine = endLine }+ | hasDeleteStartedOnSameLine startLine prevLineSplitTextEdits+ , let currLineSplitTextEdits = updateLineSplitTextEdits range s prevLineSplitTextEdits ->+ defaultParserState{ nextPragma = prevNextPragma{ lineSplitTextEdits = Just currLineSplitTextEdits } }+ | isDownwardBlockHaddock s+ , lastBlockCommentLine == startLine+ , let currLineSplitTextEdits = updateLineSplitTextEdits range s Nothing ->+ defaultParserState{+ nextPragma = prevNextPragma{ lineSplitTextEdits = Just currLineSplitTextEdits },+ mode = ModeHaddock }+ | otherwise ->+ defaultParserState{+ nextPragma = NextPragmaInfo (endLine + 1) Nothing,+ lastBlockCommentLine = endLine }+ _ -> ParserStateDone prevNextPragma+ ModeHaddock ->+ case token of+ ITvarsym "#" ->+ defaultParserState{ isLastTokenHash = True }+#if !MIN_VERSION_ghc(9,2,0)+ ITlineComment s+#else+ ITlineComment s _+#endif+ | hasDeleteStartedOnSameLine startLine prevLineSplitTextEdits+ , let currLineSplitTextEdits = updateLineSplitTextEdits range s prevLineSplitTextEdits ->+ defaultParserState{ nextPragma = prevNextPragma{ lineSplitTextEdits = Just currLineSplitTextEdits } }+ | otherwise ->+ defaultParserState+#if !MIN_VERSION_ghc(9,2,0)+ ITblockComment s+#else+ ITblockComment s _+#endif+ | isPragma s ->+ defaultParserState{+ nextPragma = NextPragmaInfo (endLine + 1) Nothing,+ mode = ModePragma,+ lastPragmaLine = endLine }+ | hasDeleteStartedOnSameLine startLine prevLineSplitTextEdits+ , let currLineSplitTextEdits = updateLineSplitTextEdits range s prevLineSplitTextEdits ->+ defaultParserState{ nextPragma = prevNextPragma{ lineSplitTextEdits = Just currLineSplitTextEdits } }+ | otherwise -> defaultParserState{ lastBlockCommentLine = endLine }+ _ -> ParserStateDone prevNextPragma+ ModePragma ->+ case token of+ ITvarsym "#" -> defaultParserState{ isLastTokenHash = True }+#if !MIN_VERSION_ghc(9,2,0)+ ITlineComment s+#else+ ITlineComment s _+#endif+ | hasDeleteStartedOnSameLine startLine prevLineSplitTextEdits+ , let currLineSplitTextEdits = updateLineSplitTextEdits range s prevLineSplitTextEdits ->+ defaultParserState{ nextPragma = prevNextPragma{ lineSplitTextEdits = Just currLineSplitTextEdits } }+ | isDownwardLineHaddock s+ , lastPragmaLine == startLine+ , let currLineSplitTextEdits = updateLineSplitTextEdits range s Nothing ->+ defaultParserState{ nextPragma = prevNextPragma{ lineSplitTextEdits = Just currLineSplitTextEdits } }+ | otherwise ->+ defaultParserState+#if !MIN_VERSION_ghc(9,2,0)+ ITblockComment s+#else+ ITblockComment s _+#endif+ | isPragma s ->+ defaultParserState{ nextPragma = NextPragmaInfo (endLine + 1) Nothing, lastPragmaLine = endLine }+ | hasDeleteStartedOnSameLine startLine prevLineSplitTextEdits+ , let currLineSplitTextEdits = updateLineSplitTextEdits range s prevLineSplitTextEdits ->+ defaultParserState{ nextPragma = prevNextPragma{ lineSplitTextEdits = Just currLineSplitTextEdits } }+ | isDownwardBlockHaddock s+ , lastPragmaLine == startLine+ , let currLineSplitTextEdits = updateLineSplitTextEdits range s Nothing ->+ defaultParserState{ nextPragma = prevNextPragma{ lineSplitTextEdits = Just currLineSplitTextEdits } }+ | lastPragmaLine == startLine && startLine < endLine+ , let currLineSplitTextEdits = updateLineSplitTextEdits range s Nothing ->+ defaultParserState{ nextPragma = prevNextPragma{ lineSplitTextEdits = Just currLineSplitTextEdits } }+ | otherwise ->+ defaultParserState{ lastBlockCommentLine = endLine }+ _ -> ParserStateDone prevNextPragma+ | otherwise = prevParserState+ where+ hasDeleteStartedOnSameLine :: Int -> Maybe LineSplitTextEdits -> Bool+ hasDeleteStartedOnSameLine line lineSplitTextEdits+ | Just lineSplitTextEdits <- lineSplitTextEdits+ , let LineSplitTextEdits{ lineSplitDeleteTextEdit } = lineSplitTextEdits+ , let LSP.TextEdit deleteRange _ = lineSplitDeleteTextEdit+ , let LSP.Range _ deleteEndPosition = deleteRange+ , let LSP.Position deleteEndLine _ = deleteEndPosition+ = fromIntegral deleteEndLine == line+ | otherwise = False++lexUntilNextLineIncl :: P (Located Token)+lexUntilNextLineIncl = do+ PState{ last_loc } <- getPState+#if MIN_VERSION_ghc(9,0,0)+ let PsSpan{ psRealSpan = lastRealSrcSpan } = last_loc+#else+ let lastRealSrcSpan = last_loc+#endif+ let prevEndLine = lastRealSrcSpan & realSrcSpanEnd & srcLocLine+ locatedToken@(L srcSpan _token) <- lexer False pure+ if | RealSrcLoc currEndRealSrcLoc _ <- srcSpan & srcSpanEnd+ , let currEndLine = currEndRealSrcLoc & srcLocLine+ -> if prevEndLine < currEndLine then+ pure locatedToken+ else lexUntilNextLineIncl+ | otherwise -> pure locatedToken++dropWhileStringBuffer :: (Char -> Bool) -> StringBuffer -> StringBuffer+dropWhileStringBuffer predicate buffer+ | atEnd buffer = buffer+ | let (c, remainingBuffer) = nextChar buffer+ = if predicate c then+ dropWhileStringBuffer predicate remainingBuffer+ else+ buffer++isHorizontalSpace :: Char -> Bool+isHorizontalSpace c = c == ' ' || c == '\t'++data ShebangParserState = ShebangParserState {+ nextPragmaLine :: !Int,+ newlineCount :: !Int,+ prevCharIsHash :: !Bool,+ buffer :: !StringBuffer+}++-- lexer seems to ignore shebangs completely hence this function+parseShebangs :: ShebangParserState -> ShebangParserState+parseShebangs prev@ShebangParserState{ newlineCount = prevNewlineCount, prevCharIsHash, buffer = prevBuffer }+ | atEnd prevBuffer+ = prev+ | let (c, currBuffer) = nextChar (dropWhileStringBuffer isHorizontalSpace prevBuffer)+ = if c == '#' then+ parseShebangs prev{ prevCharIsHash = True, buffer = currBuffer }+ else if c == '!' && prevCharIsHash then+ parseShebangs prev{ nextPragmaLine = prevNewlineCount + 1, buffer = dropWhileStringBuffer (/= '\n') currBuffer }+ else if c == '\n' then+ parseShebangs prev{ newlineCount = prevNewlineCount + 1, buffer = currBuffer }+ else+ prev+++-- | Parses blank lines, comments, haddock comments ("-- |"), lines that start+-- with "#!", lines that start with "#", pragma lines using the GHC API lexer.+-- When it doesn't find one of these things then it's assumed that we've found+-- a declaration, end-of-file, or a ghc parse error, and the parser stops.+-- Shebangs are parsed separately than the rest becaues the lexer ignores them.+--+-- The reason for custom parsing instead of using annotations, or turning on/off+-- extensions in the dynflags is because there are a number of extensions that+-- while removing parse errors, can also introduce them. Hence, there are+-- cases where the file cannot be parsed without error when we want to insert+-- extension (and other) pragmas. The compiler (8.10.7) doesn't include+-- annotations in its failure state. So if the compiler someday returns+-- annotation or equivalent information when it fails then we can replace this+-- with that.+--+-- The reason for using the compiler lexer is to reduce duplicated+-- implementation, particularly nested comments, but in retrospect this comes+-- with the disadvantage of the logic feeling more complex, and not being able+-- to handle whitespace directly.+--+-- The parser keeps track of state in order to place the next pragma line+-- according to some rules:+--+-- - Ignore lines starting with '#' except for shebangs.+-- - If pragmas exist place after last pragma+-- - else if haddock comments exist:+-- - If comments exist place after last comment+-- - else if shebangs exist place after last shebang+-- - else place at first line+-- - else if comments exist place after last comment+-- - else if shebangs exist place after last shebang+-- - else place at first line+--+-- Additionally the parser keeps track of information to be able to insert+-- pragmas inbetween lines.+--+-- For example the parser keeps track of information so that+--+-- > {- block comment -} -- | haddock+--+-- can become+--+-- > {- block comment -}+-- > {-# pragma #-}+-- > -- | haddock+--+-- This information does not respect the type of whitespace, because the lexer+-- strips whitespace and gives locations.+--+-- In this example the tabs are converted to spaces in the TextEdits:+--+-- > {- block comment -}<space><tab><tab><space>-- | haddock+--+parsePreDecl :: DynFlags -> StringBuffer -> ParseResult ParserState+parsePreDecl dynFlags buffer = unP (go initialParserState) pState+ where+ initialShebangParserState = ShebangParserState{+ nextPragmaLine = 0,+ newlineCount = 0,+ prevCharIsHash = False,+ buffer = buffer }+ ShebangParserState{ nextPragmaLine } = parseShebangs initialShebangParserState+ pState = mkLexerPState dynFlags buffer+ initialParserState = ParserStateNotDone (NextPragmaInfo nextPragmaLine Nothing) ModeInitial (-1) (-1) False++ go :: ParserState -> P ParserState+ go prevParserState =+ case prevParserState of+ ParserStateDone _ -> pure prevParserState+ ParserStateNotDone{..} -> do+ L srcSpan token <-+ if isLastTokenHash then+ lexUntilNextLineIncl+ else+ lexer False pure+ case srcSpanToRange srcSpan of+ Just range -> go (updateParserState token range prevParserState)+ Nothing -> pure prevParserState++mkLexerPState :: DynFlags -> StringBuffer -> PState+mkLexerPState dynFlags stringBuffer =+ let+ startRealSrcLoc = mkRealSrcLoc "asdf" 1 1+ updateDynFlags = flip gopt_unset Opt_Haddock . flip gopt_set Opt_KeepRawTokenStream+ finalDynFlags = updateDynFlags dynFlags+#if !MIN_VERSION_ghc(8,8,1)+ pState = mkPState finalDynFlags stringBuffer startRealSrcLoc+ finalPState = pState{ use_pos_prags = False }+#elif !MIN_VERSION_ghc(8,10,1)+ mkLexerParserFlags =+ mkParserFlags'+ <$> warningFlags+ <*> extensionFlags+ <*> homeUnitId_+ <*> safeImportsOn+ <*> gopt Opt_Haddock+ <*> gopt Opt_KeepRawTokenStream+ <*> const False+ finalPState = mkPStatePure (mkLexerParserFlags finalDynFlags) stringBuffer startRealSrcLoc+#else+ pState = initParserState (initParserOpts finalDynFlags) stringBuffer startRealSrcLoc+ PState{ options = pStateOptions } = pState+ finalExtBitsMap = setBit (pExtsBitmap pStateOptions) (fromEnum UsePosPragsBit)+ finalPStateOptions = pStateOptions{ pExtsBitmap = finalExtBitsMap }+ finalPState = pState{ options = finalPStateOptions }+#endif+ in+ finalPState
src/Development/IDE/Types/Diagnostics.hs view
@@ -92,7 +92,7 @@ prettyRange :: Range -> Doc Terminal.AnsiStyle prettyRange Range{..} = f _start <> "-" <> f _end- where f Position{..} = pretty (_line+1) <> colon <> pretty (_character+1)+ where f Position{..} = pretty (show $ _line+1) <> colon <> pretty (show $ _character+1) stringParagraphs :: T.Text -> Doc a stringParagraphs = vcat . map (fillSep . map pretty . T.words) . T.lines
src/Development/IDE/Types/Exports.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE RankNTypes #-} module Development.IDE.Types.Exports ( IdentInfo(..),@@ -26,6 +27,7 @@ import Development.IDE.GHC.Compat import Development.IDE.GHC.Orphans () import Development.IDE.GHC.Util+import Development.IDE.Types.Shake (WithHieDb) import GHC.Generics (Generic) import HieDb @@ -154,13 +156,13 @@ nonInternalModules :: ModuleName -> Bool nonInternalModules = not . (".Internal" `isSuffixOf`) . moduleNameString -createExportsMapHieDb :: HieDb -> IO ExportsMap-createExportsMapHieDb hiedb = do- mods <- getAllIndexedMods hiedb+createExportsMapHieDb :: WithHieDb -> IO ExportsMap+createExportsMapHieDb withHieDb = do+ mods <- withHieDb getAllIndexedMods idents <- forM (filter (nonInternalModules . modInfoName . hieModInfo) mods) $ \m -> do let mn = modInfoName $ hieModInfo m mText = pack $ moduleNameString mn- fmap (wrap . unwrap mText) <$> getExportsForModule hiedb mn+ fmap (wrap . unwrap mText) <$> withHieDb (\hieDb -> getExportsForModule hieDb mn) let exportsMap = Map.fromListWith (<>) (concat idents) return $ ExportsMap exportsMap $ buildModuleExportMap (concat idents) where
src/Development/IDE/Types/HscEnvEq.hs view
@@ -1,151 +1,153 @@-module Development.IDE.Types.HscEnvEq-( HscEnvEq,- hscEnv, newHscEnvEq,- hscEnvWithImportPaths,- newHscEnvEqPreserveImportPaths,- newHscEnvEqWithImportPaths,- envImportPaths,- envPackageExports,- envVisibleModuleNames,- deps-) where---import Control.Concurrent.Async (Async, async, waitCatch)-import Control.Concurrent.Strict (modifyVar, newVar)-import Control.DeepSeq (force)-import Control.Exception (evaluate, mask, throwIO)-import Control.Monad.Extra (eitherM, join, mapMaybeM)-import Control.Monad.IO.Class-import Data.Either (fromRight)-import Data.Set (Set)-import qualified Data.Set as Set-import Data.Unique (Unique)-import qualified Data.Unique as Unique-import Development.IDE.GHC.Compat-import qualified Development.IDE.GHC.Compat.Util as Maybes-import Development.IDE.GHC.Error (catchSrcErrors)-import Development.IDE.GHC.Util (lookupPackageConfig)-import Development.IDE.Graph.Classes-import Development.IDE.Types.Exports (ExportsMap, createExportsMap)-import OpenTelemetry.Eventlog (withSpan)-import System.Directory (canonicalizePath)-import System.FilePath---- | An 'HscEnv' with equality. Two values are considered equal--- if they are created with the same call to 'newHscEnvEq'.-data HscEnvEq = HscEnvEq- { envUnique :: !Unique- , hscEnv :: !HscEnv- , deps :: [(UnitId, DynFlags)]- -- ^ In memory components for this HscEnv- -- This is only used at the moment for the import dirs in- -- the DynFlags- , envImportPaths :: Maybe (Set FilePath)- -- ^ If Just, import dirs originally configured in this env- -- If Nothing, the env import dirs are unaltered- , envPackageExports :: IO ExportsMap- , envVisibleModuleNames :: IO (Maybe [ModuleName])- -- ^ 'listVisibleModuleNames' is a pure function,- -- but it could panic due to a ghc bug: https://github.com/haskell/haskell-language-server/issues/1365- -- So it's wrapped in IO here for error handling- -- If Nothing, 'listVisibleModuleNames' panic- }---- | Wrap an 'HscEnv' into an 'HscEnvEq'.-newHscEnvEq :: FilePath -> HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq-newHscEnvEq cradlePath hscEnv0 deps = do- let relativeToCradle = (takeDirectory cradlePath </>)- hscEnv = removeImportPaths hscEnv0-- -- Canonicalize import paths since we also canonicalize targets- importPathsCanon <-- mapM canonicalizePath $ relativeToCradle <$> importPaths (hsc_dflags hscEnv0)-- newHscEnvEqWithImportPaths (Just $ Set.fromList importPathsCanon) hscEnv deps--newHscEnvEqWithImportPaths :: Maybe (Set FilePath) -> HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq-newHscEnvEqWithImportPaths envImportPaths hscEnv deps = do-- let dflags = hsc_dflags hscEnv-- envUnique <- Unique.newUnique-- -- it's very important to delay the package exports computation- envPackageExports <- onceAsync $ withSpan "Package Exports" $ \_sp -> do- -- compute the package imports- let pkgst = unitState hscEnv- depends = explicitUnits pkgst- targets =- [ (pkg, mn)- | d <- depends- , Just pkg <- [lookupPackageConfig d hscEnv]- , (mn, _) <- unitExposedModules pkg- ]-- doOne (pkg, mn) = do- modIface <- liftIO $ initIfaceLoad hscEnv $ loadInterface- ""- (mkModule (unitInfoId pkg) mn)- (ImportByUser NotBoot)- return $ case modIface of- Maybes.Failed _r -> Nothing- Maybes.Succeeded mi -> Just mi- modIfaces <- mapMaybeM doOne targets- return $ createExportsMap modIfaces-- -- similar to envPackageExports, evaluated lazily- envVisibleModuleNames <- onceAsync $- fromRight Nothing- <$> catchSrcErrors- dflags- "listVisibleModuleNames"- (evaluate . force . Just $ listVisibleModuleNames hscEnv)-- return HscEnvEq{..}---- | Wrap an 'HscEnv' into an 'HscEnvEq'.-newHscEnvEqPreserveImportPaths- :: HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq-newHscEnvEqPreserveImportPaths = newHscEnvEqWithImportPaths Nothing---- | Unwrap the 'HscEnv' with the original import paths.--- Used only for locating imports-hscEnvWithImportPaths :: HscEnvEq -> HscEnv-hscEnvWithImportPaths HscEnvEq{..}- | Just imps <- envImportPaths- = hscSetFlags (setImportPaths (Set.toList imps) (hsc_dflags hscEnv)) hscEnv- | otherwise- = hscEnv--removeImportPaths :: HscEnv -> HscEnv-removeImportPaths hsc = hscSetFlags (setImportPaths [] (hsc_dflags hsc)) hsc--instance Show HscEnvEq where- show HscEnvEq{envUnique} = "HscEnvEq " ++ show (Unique.hashUnique envUnique)--instance Eq HscEnvEq where- a == b = envUnique a == envUnique b--instance NFData HscEnvEq where- rnf (HscEnvEq a b c d _ _) =- -- deliberately skip the package exports map and visible module names- rnf (Unique.hashUnique a) `seq` b `seq` c `seq` rnf d--instance Hashable HscEnvEq where- hashWithSalt s = hashWithSalt s . envUnique---- | Given an action, produce a wrapped action that runs at most once.--- The action is run in an async so it won't be killed by async exceptions--- If the function raises an exception, the same exception will be reraised each time.-onceAsync :: IO a -> IO (IO a)-onceAsync act = do- var <- newVar OncePending- let run as = eitherM throwIO pure (waitCatch as)- pure $ mask $ \unmask -> join $ modifyVar var $ \v -> case v of- OnceRunning x -> pure (v, unmask $ run x)- OncePending -> do- x <- async (unmask act)- pure (OnceRunning x, unmask $ run x)--data Once a = OncePending | OnceRunning (Async a)+module Development.IDE.Types.HscEnvEq +( HscEnvEq, + hscEnv, newHscEnvEq, + hscEnvWithImportPaths, + newHscEnvEqPreserveImportPaths, + newHscEnvEqWithImportPaths, + envImportPaths, + envPackageExports, + envVisibleModuleNames, + deps +) where + + +import Control.Concurrent.Async (Async, async, waitCatch) +import Control.Concurrent.Strict (modifyVar, newVar) +import Control.DeepSeq (force) +import Control.Exception (evaluate, mask, throwIO) +import Control.Monad.Extra (eitherM, join, mapMaybeM) +import Data.Either (fromRight) +import Data.Set (Set) +import qualified Data.Set as Set +import Data.Unique (Unique) +import qualified Data.Unique as Unique +import Development.IDE.GHC.Compat +import qualified Development.IDE.GHC.Compat.Util as Maybes +import Development.IDE.GHC.Error (catchSrcErrors) +import Development.IDE.GHC.Util (lookupPackageConfig) +import Development.IDE.Graph.Classes +import Development.IDE.Types.Exports (ExportsMap, createExportsMap) +import OpenTelemetry.Eventlog (withSpan) +import System.Directory (makeAbsolute) +import System.FilePath + +-- | An 'HscEnv' with equality. Two values are considered equal +-- if they are created with the same call to 'newHscEnvEq'. +data HscEnvEq = HscEnvEq + { envUnique :: !Unique + , hscEnv :: !HscEnv + , deps :: [(UnitId, DynFlags)] + -- ^ In memory components for this HscEnv + -- This is only used at the moment for the import dirs in + -- the DynFlags + , envImportPaths :: Maybe (Set FilePath) + -- ^ If Just, import dirs originally configured in this env + -- If Nothing, the env import dirs are unaltered + , envPackageExports :: IO ExportsMap + , envVisibleModuleNames :: IO (Maybe [ModuleName]) + -- ^ 'listVisibleModuleNames' is a pure function, + -- but it could panic due to a ghc bug: https://github.com/haskell/haskell-language-server/issues/1365 + -- So it's wrapped in IO here for error handling + -- If Nothing, 'listVisibleModuleNames' panic + } + +-- | Wrap an 'HscEnv' into an 'HscEnvEq'. +newHscEnvEq :: FilePath -> HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq +newHscEnvEq cradlePath hscEnv0 deps = do + let relativeToCradle = (takeDirectory cradlePath </>) + hscEnv = removeImportPaths hscEnv0 + + -- Make Absolute since targets are also absolute + importPathsCanon <- + mapM makeAbsolute $ relativeToCradle <$> importPaths (hsc_dflags hscEnv0) + + newHscEnvEqWithImportPaths (Just $ Set.fromList importPathsCanon) hscEnv deps + +newHscEnvEqWithImportPaths :: Maybe (Set FilePath) -> HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq +newHscEnvEqWithImportPaths envImportPaths hscEnv deps = do + + let dflags = hsc_dflags hscEnv + + envUnique <- Unique.newUnique + + -- it's very important to delay the package exports computation + envPackageExports <- onceAsync $ withSpan "Package Exports" $ \_sp -> do + -- compute the package imports + let pkgst = unitState hscEnv + depends = explicitUnits pkgst + modules = + [ m + | d <- depends + , Just pkg <- [lookupPackageConfig d hscEnv] + , (modName, maybeOtherPkgMod) <- unitExposedModules pkg + , let m = case maybeOtherPkgMod of + -- When module is re-exported from another package, + -- the origin module is represented by value in Just + Just otherPkgMod -> otherPkgMod + Nothing -> mkModule (unitInfoId pkg) modName + ] + + doOne m = do + modIface <- initIfaceLoad hscEnv $ + loadInterface "" m (ImportByUser NotBoot) + return $ case modIface of + Maybes.Failed _r -> Nothing + Maybes.Succeeded mi -> Just mi + modIfaces <- mapMaybeM doOne modules + return $ createExportsMap modIfaces + + -- similar to envPackageExports, evaluated lazily + envVisibleModuleNames <- onceAsync $ + fromRight Nothing + <$> catchSrcErrors + dflags + "listVisibleModuleNames" + (evaluate . force . Just $ listVisibleModuleNames hscEnv) + + return HscEnvEq{..} + +-- | Wrap an 'HscEnv' into an 'HscEnvEq'. +newHscEnvEqPreserveImportPaths + :: HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq +newHscEnvEqPreserveImportPaths = newHscEnvEqWithImportPaths Nothing + +-- | Unwrap the 'HscEnv' with the original import paths. +-- Used only for locating imports +hscEnvWithImportPaths :: HscEnvEq -> HscEnv +hscEnvWithImportPaths HscEnvEq{..} + | Just imps <- envImportPaths + = hscSetFlags (setImportPaths (Set.toList imps) (hsc_dflags hscEnv)) hscEnv + | otherwise + = hscEnv + +removeImportPaths :: HscEnv -> HscEnv +removeImportPaths hsc = hscSetFlags (setImportPaths [] (hsc_dflags hsc)) hsc + +instance Show HscEnvEq where + show HscEnvEq{envUnique} = "HscEnvEq " ++ show (Unique.hashUnique envUnique) + +instance Eq HscEnvEq where + a == b = envUnique a == envUnique b + +instance NFData HscEnvEq where + rnf (HscEnvEq a b c d _ _) = + -- deliberately skip the package exports map and visible module names + rnf (Unique.hashUnique a) `seq` b `seq` c `seq` rnf d + +instance Hashable HscEnvEq where + hashWithSalt s = hashWithSalt s . envUnique + +-- | Given an action, produce a wrapped action that runs at most once. +-- The action is run in an async so it won't be killed by async exceptions +-- If the function raises an exception, the same exception will be reraised each time. +onceAsync :: IO a -> IO (IO a) +onceAsync act = do + var <- newVar OncePending + let run as = eitherM throwIO pure (waitCatch as) + pure $ mask $ \unmask -> join $ modifyVar var $ \v -> case v of + OnceRunning x -> pure (v, unmask $ run x) + OncePending -> do + x <- async (unmask act) + pure (OnceRunning x, unmask $ run x) + +data Once a = OncePending | OnceRunning (Async a)
src/Development/IDE/Types/Logger.hs view
@@ -31,7 +31,7 @@ -- | Note that this is logging actions _of the program_, not of the user. -- You shouldn't call warning/error if the user has caused an error, only -- if our code has gone wrong and is itself erroneous (e.g. we threw an exception).-data Logger = Logger {logPriority :: Priority -> T.Text -> IO ()}+newtype Logger = Logger {logPriority :: Priority -> T.Text -> IO ()} instance Semigroup Logger where l1 <> l2 = Logger $ \p t -> logPriority l1 p t >> logPriority l2 p t
src/Development/IDE/Types/Shake.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} module Development.IDE.Types.Shake ( Q (..),@@ -13,14 +14,13 @@ ShakeValue(..), currentValue, isBadDependency,- toShakeValue,encodeShakeValue,decodeShakeValue,toKey,toNoFileKey,fromKey,fromKeyType)+ toShakeValue,encodeShakeValue,decodeShakeValue,toKey,toNoFileKey,fromKey,fromKeyType,WithHieDb) where import Control.DeepSeq import Control.Exception import qualified Data.ByteString.Char8 as BS import Data.Dynamic-import Data.HashMap.Strict import Data.Hashable import Data.Typeable (cast) import Data.Vector (Vector)@@ -30,13 +30,19 @@ import Development.IDE.Types.Diagnostics import Development.IDE.Types.Location import GHC.Generics+import HieDb.Types (HieDb) import Language.LSP.Types+import qualified StmContainers.Map as STM import Type.Reflection (SomeTypeRep (SomeTypeRep), pattern App, pattern Con, typeOf, typeRep, typeRepTyCon) import Unsafe.Coerce (unsafeCoerce) +-- | Intended to represent HieDb calls wrapped with (currently) retry+-- functionality+type WithHieDb = forall a. (HieDb -> IO a) -> IO a+ data Value v = Succeeded TextDocumentVersion v | Stale (Maybe PositionDelta) TextDocumentVersion v@@ -56,7 +62,7 @@ = ValueWithDiagnostics !(Value Dynamic) !(Vector FileDiagnostic) -- | The state of the all values and diagnostics-type Values = HashMap Key ValueWithDiagnostics+type Values = STM.Map Key ValueWithDiagnostics -- | When we depend on something that reported an error, and we fail as a direct result, throw BadDependency -- which short-circuits the rest of the action
src/Generics/SYB/GHC.hs view
@@ -1,124 +1,124 @@-{-# LANGUAGE DerivingVia #-}-{-# LANGUAGE RankNTypes #-}---- | Custom SYB traversals explicitly designed for operating over the GHC AST.-module Generics.SYB.GHC- ( genericIsSubspan,- mkBindListT,- everywhereM',- smallestM,- largestM- ) where--import Control.Monad-import Data.Functor.Compose (Compose (Compose))-import Data.Monoid (Any (Any))-import Development.IDE.GHC.Compat-import Development.IDE.Graph.Classes-import Generics.SYB----- | A generic query intended to be used for calling 'smallestM' and--- 'largestM'. If the current node is a 'Located', returns whether or not the--- given 'SrcSpan' is a subspan. For all other nodes, returns 'Nothing', which--- indicates uncertainty. The search strategy in 'smallestM' et al. will--- continue searching uncertain nodes.-genericIsSubspan ::- forall ast.- Typeable ast =>- -- | The type of nodes we'd like to consider.- Proxy (Located ast) ->- SrcSpan ->- GenericQ (Maybe (Bool, ast))-genericIsSubspan _ dst = mkQ Nothing $ \case- (L span ast :: Located ast) -> Just (dst `isSubspanOf` span, ast)----- | Lift a function that replaces a value with several values into a generic--- function. The result doesn't perform any searching, so should be driven via--- 'everywhereM' or friends.------ The 'Int' argument is the index in the list being bound.-mkBindListT :: forall b m. (Data b, Monad m) => (Int -> b -> m [b]) -> GenericM m-mkBindListT f = mkM $ fmap join . traverse (uncurry f) . zip [0..]----- | Apply a monadic transformation everywhere in a top-down manner.-everywhereM' :: forall m. Monad m => GenericM m -> GenericM m-everywhereM' f = go- where- go :: GenericM m- go = gmapM go <=< f------------------------------------------------------------------------------------ Custom SYB machinery----------------------------------------------------------------------------------- | Generic monadic transformations that return side-channel data.-type GenericMQ r m = forall a. Data a => a -> m (r, a)----------------------------------------------------------------------------------- | Apply the given 'GenericM' at all every node whose children fail the--- 'GenericQ', but which passes the query itself.------ The query must be a monotonic function when it returns 'Just'. That is, if--- @s@ is a subtree of @t@, @q t@ should return @Just True@ if @q s@ does. It--- is the True-to-false edge of the query that triggers the transformation.------ Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes--- with data nodes, so for any given node we can only definitely return an--- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is--- used.-smallestM :: forall m a. Monad m => GenericQ (Maybe (Bool, a)) -> (a -> GenericM m) -> GenericM m-smallestM q f = fmap snd . go- where- go :: GenericMQ Any m- go x = do- case q x of- Nothing -> gmapMQ go x- Just (True, a) -> do- it@(r, x') <- gmapMQ go x- case r of- Any True -> pure it- Any False -> fmap (Any True,) $ f a x'- Just (False, _) -> pure (mempty, x)----------------------------------------------------------------------------------- | Apply the given 'GenericM' at every node that passes the 'GenericQ', but--- don't descend into children if the query matches. Because this traversal is--- root-first, this policy will find the largest subtrees for which the query--- holds true.------ Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes--- with data nodes, so for any given node we can only definitely return an--- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is--- used.-largestM :: forall m a. Monad m => GenericQ (Maybe (Bool, a)) -> (a -> GenericM m) -> GenericM m-largestM q f = go- where- go :: GenericM m- go x = do- case q x of- Just (True, a) -> f a x- Just (False, _) -> pure x- Nothing -> gmapM go x--newtype MonadicQuery r m a = MonadicQuery- { runMonadicQuery :: m (r, a)- }- deriving stock (Functor)- deriving Applicative via Compose m ((,) r)------------------------------------------------------------------------------------ | Like 'gmapM', but also returns side-channel data.-gmapMQ ::- forall f r a. (Monoid r, Data a, Applicative f) =>- (forall d. Data d => d -> f (r, d)) ->- a ->- f (r, a)-gmapMQ f = runMonadicQuery . gfoldl k pure- where- k :: Data d => MonadicQuery r f (d -> b) -> d -> MonadicQuery r f b- k c x = c <*> MonadicQuery (f x)+{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE RankNTypes #-} + +-- | Custom SYB traversals explicitly designed for operating over the GHC AST. +module Generics.SYB.GHC + ( genericIsSubspan, + mkBindListT, + everywhereM', + smallestM, + largestM + ) where + +import Control.Monad +import Data.Functor.Compose (Compose (Compose)) +import Data.Monoid (Any (Any)) +import Development.IDE.GHC.Compat +import Development.IDE.Graph.Classes +import Generics.SYB + + +-- | A generic query intended to be used for calling 'smallestM' and +-- 'largestM'. If the current node is a 'Located', returns whether or not the +-- given 'SrcSpan' is a subspan. For all other nodes, returns 'Nothing', which +-- indicates uncertainty. The search strategy in 'smallestM' et al. will +-- continue searching uncertain nodes. +genericIsSubspan :: + forall ast. + Typeable ast => + -- | The type of nodes we'd like to consider. + Proxy (Located ast) -> + SrcSpan -> + GenericQ (Maybe (Bool, ast)) +genericIsSubspan _ dst = mkQ Nothing $ \case + (L span ast :: Located ast) -> Just (dst `isSubspanOf` span, ast) + + +-- | Lift a function that replaces a value with several values into a generic +-- function. The result doesn't perform any searching, so should be driven via +-- 'everywhereM' or friends. +-- +-- The 'Int' argument is the index in the list being bound. +mkBindListT :: forall b m. (Data b, Monad m) => (Int -> b -> m [b]) -> GenericM m +mkBindListT f = mkM $ fmap join . traverse (uncurry f) . zip [0..] + + +-- | Apply a monadic transformation everywhere in a top-down manner. +everywhereM' :: forall m. Monad m => GenericM m -> GenericM m +everywhereM' f = go + where + go :: GenericM m + go = gmapM go <=< f + + +------------------------------------------------------------------------------ +-- Custom SYB machinery +------------------------------------------------------------------------------ + +-- | Generic monadic transformations that return side-channel data. +type GenericMQ r m = forall a. Data a => a -> m (r, a) + +------------------------------------------------------------------------------ +-- | Apply the given 'GenericM' at all every node whose children fail the +-- 'GenericQ', but which passes the query itself. +-- +-- The query must be a monotonic function when it returns 'Just'. That is, if +-- @s@ is a subtree of @t@, @q t@ should return @Just True@ if @q s@ does. It +-- is the True-to-false edge of the query that triggers the transformation. +-- +-- Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes +-- with data nodes, so for any given node we can only definitely return an +-- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is +-- used. +smallestM :: forall m a. Monad m => GenericQ (Maybe (Bool, a)) -> (a -> GenericM m) -> GenericM m +smallestM q f = fmap snd . go + where + go :: GenericMQ Any m + go x = do + case q x of + Nothing -> gmapMQ go x + Just (True, a) -> do + it@(r, x') <- gmapMQ go x + case r of + Any True -> pure it + Any False -> fmap (Any True,) $ f a x' + Just (False, _) -> pure (mempty, x) + +------------------------------------------------------------------------------ +-- | Apply the given 'GenericM' at every node that passes the 'GenericQ', but +-- don't descend into children if the query matches. Because this traversal is +-- root-first, this policy will find the largest subtrees for which the query +-- holds true. +-- +-- Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes +-- with data nodes, so for any given node we can only definitely return an +-- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is +-- used. +largestM :: forall m a. Monad m => GenericQ (Maybe (Bool, a)) -> (a -> GenericM m) -> GenericM m +largestM q f = go + where + go :: GenericM m + go x = do + case q x of + Just (True, a) -> f a x + Just (False, _) -> pure x + Nothing -> gmapM go x + +newtype MonadicQuery r m a = MonadicQuery + { runMonadicQuery :: m (r, a) + } + deriving stock (Functor) + deriving Applicative via Compose m ((,) r) + + +------------------------------------------------------------------------------ +-- | Like 'gmapM', but also returns side-channel data. +gmapMQ :: + forall f r a. (Monoid r, Data a, Applicative f) => + (forall d. Data d => d -> f (r, d)) -> + a -> + f (r, a) +gmapMQ f = runMonadicQuery . gfoldl k pure + where + k :: Data d => MonadicQuery r f (d -> b) -> d -> MonadicQuery r f b + k c x = c <*> MonadicQuery (f x)
src/Text/Fuzzy/Parallel.hs view
@@ -132,7 +132,7 @@ chunkVector :: Int -> Vector a -> [Vector a] chunkVector chunkSize v = do let indices = chunkIndices chunkSize (0,V.length v)- [V.slice l (h-l) v | (l,h) <- indices]+ [V.slice l (h-l+1) v | (l,h) <- indices] -- >>> chunkIndices 3 (0,9) -- >>> chunkIndices 3 (0,10)
− test/data/cabal-exe/dist-newstyle/build/x86_64-osx/ghc-8.10.3/a-0.1.0.0/x/a/build/a/autogen/Paths_a.hs
@@ -1,50 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE NoRebindableSyntax #-}-{-# OPTIONS_GHC -fno-warn-missing-import-lists #-}-module Paths_a (- version,- getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir,- getDataFileName, getSysconfDir- ) where--import qualified Control.Exception as Exception-import Data.Version (Version(..))-import System.Environment (getEnv)-import Prelude--#if defined(VERSION_base)--#if MIN_VERSION_base(4,0,0)-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#else-catchIO :: IO a -> (Exception.Exception -> IO a) -> IO a-#endif--#else-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#endif-catchIO = Exception.catch--version :: Version-version = Version [0,1,0,0] []-bindir, libdir, dynlibdir, datadir, libexecdir, sysconfdir :: FilePath--bindir = "/Users/pepeiborra/.cabal/bin"-libdir = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.3/a-0.1.0.0-inplace-a"-dynlibdir = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.3"-datadir = "/Users/pepeiborra/.cabal/share/x86_64-osx-ghc-8.10.3/a-0.1.0.0"-libexecdir = "/Users/pepeiborra/.cabal/libexec/x86_64-osx-ghc-8.10.3/a-0.1.0.0"-sysconfdir = "/Users/pepeiborra/.cabal/etc"--getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir, getSysconfDir :: IO FilePath-getBinDir = catchIO (getEnv "a_bindir") (\_ -> return bindir)-getLibDir = catchIO (getEnv "a_libdir") (\_ -> return libdir)-getDynLibDir = catchIO (getEnv "a_dynlibdir") (\_ -> return dynlibdir)-getDataDir = catchIO (getEnv "a_datadir") (\_ -> return datadir)-getLibexecDir = catchIO (getEnv "a_libexecdir") (\_ -> return libexecdir)-getSysconfDir = catchIO (getEnv "a_sysconfdir") (\_ -> return sysconfdir)--getDataFileName :: FilePath -> IO FilePath-getDataFileName name = do- dir <- getDataDir- return (dir ++ "/" ++ name)
+ test/data/import-placement/MultiLinePragma.expected.hs view
@@ -0,0 +1,13 @@+{-# OPTIONS_GHC -Wall #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards, + OverloadedStrings #-}+{-# OPTIONS_GHC -Wall,+ -Wno-unused-imports #-}+import Data.Monoid+++-- some comment+class Semigroup a => SomeData a++instance SomeData All
+ test/data/import-placement/MultiLinePragma.hs view
@@ -0,0 +1,12 @@+{-# OPTIONS_GHC -Wall #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards, + OverloadedStrings #-}+{-# OPTIONS_GHC -Wall,+ -Wno-unused-imports #-}+++-- some comment+class Semigroup a => SomeData a++instance SomeData All
+ test/data/import-placement/OptionsNotAtTopWithSpaces.expected.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE OverloadedStrings #-}+++{-# LANGUAGE TupleSections #-}+import Data.Monoid+++++class Semigroup a => SomeData a+instance SomeData All++{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/OptionsNotAtTopWithSpaces.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE OverloadedStrings #-}+++{-# LANGUAGE TupleSections #-}+++++class Semigroup a => SomeData a+instance SomeData All++{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/OptionsPragmaNotAtTop.expected.hs view
@@ -0,0 +1,8 @@+import Data.Monoid+class Semigroup a => SomeData a+instance SomeData All++{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/OptionsPragmaNotAtTop.hs view
@@ -0,0 +1,7 @@+class Semigroup a => SomeData a+instance SomeData All++{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/PragmaNotAtTopMultipleComments.expected.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE OverloadedStrings #-}++{-# OPTIONS_GHC -Wall,+ OPTIONS_GHC -Wno-unrecognised-pragmas #-}+-- another comment+-- oh+{- multi line +comment+-}++{-# LANGUAGE TupleSections #-}+import Data.Monoid+{- some comment -}++-- again+class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/PragmaNotAtTopMultipleComments.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings #-}++{-# OPTIONS_GHC -Wall,+ OPTIONS_GHC -Wno-unrecognised-pragmas #-}+-- another comment+-- oh+{- multi line +comment+-}++{-# LANGUAGE TupleSections #-}+{- some comment -}++-- again+class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/PragmaNotAtTopWithCommentsAtTop.expected.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE OverloadedStrings #-}++{-# OPTIONS_GHC -Wall #-}+-- another comment++{-# LANGUAGE TupleSections #-}+import Data.Monoid+{- some comment -}+++class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/PragmaNotAtTopWithCommentsAtTop.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE OverloadedStrings #-}++{-# OPTIONS_GHC -Wall #-}+-- another comment++{-# LANGUAGE TupleSections #-}+{- some comment -}+++class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/PragmaNotAtTopWithImports.expected.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wall #-}++{-# LANGUAGE TupleSections #-}+module Test+( SomeData(..)+) where+import Data.Text+import Data.Monoid+++class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/PragmaNotAtTopWithImports.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wall #-}++{-# LANGUAGE TupleSections #-}+module Test+( SomeData(..)+) where+import Data.Text+++class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/PragmaNotAtTopWithModuleDecl.expected.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings #-}+++{-# OPTIONS_GHC -Wall #-}++{-# LANGUAGE TupleSections #-}+module Test+( SomeData(..)+) where+import Data.Monoid+++++class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/PragmaNotAtTopWithModuleDecl.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE OverloadedStrings #-}+++{-# OPTIONS_GHC -Wall #-}++{-# LANGUAGE TupleSections #-}+module Test+( SomeData(..)+) where+++++class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/ShebangNotAtTop.expected.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE OverloadedStrings #-}+import Data.Monoid++class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"++f :: Int -> Int +f x = x * x
+ test/data/import-placement/ShebangNotAtTop.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE OverloadedStrings #-}++class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"++f :: Int -> Int +f x = x * x
+ test/data/import-placement/ShebangNotAtTopNoSpace.expected.hs view
@@ -0,0 +1,8 @@+import Data.Monoid+class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"++f :: Int -> Int +f x = x * x
+ test/data/import-placement/ShebangNotAtTopNoSpace.hs view
@@ -0,0 +1,7 @@+class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"++f :: Int -> Int +f x = x * x
+ test/data/import-placement/ShebangNotAtTopWithSpaces.expected.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE OverloadedStrings #-}+++{-# OPTIONS_GHC -Wall #-}++++{-# LANGUAGE TupleSections #-}+import Data.Monoid+++++class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/ShebangNotAtTopWithSpaces.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE OverloadedStrings #-}+++{-# OPTIONS_GHC -Wall #-}++++{-# LANGUAGE TupleSections #-}+++++class Semigroup a => SomeData a+instance SomeData All++#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++addOne :: Int -> Int+addOne x = x + 1
+ test/data/import-placement/WhereDeclLowerInFile.expected.hs view
@@ -0,0 +1,18 @@+module Asdf + (f+ , where') + + where+import Data.Int++++f :: Int64 -> Int64+f = id'+ where id' = id++g :: Int -> Int +g = id++where' :: Int -> Int +where' = id
+ test/data/import-placement/WhereDeclLowerInFile.hs view
@@ -0,0 +1,17 @@+module Asdf + (f+ , where') + + where++++f :: Int64 -> Int64+f = id'+ where id' = id++g :: Int -> Int +g = id++where' :: Int -> Int +where' = id
+ test/data/import-placement/WhereKeywordLowerInFileNoExports.expected.hs view
@@ -0,0 +1,16 @@+module Asdf + + + where+import Data.Int+++f :: Int64 -> Int64+f = id'+ where id' = id++g :: Int -> Int +g = id++where' :: Int -> Int +where' = id
+ test/data/import-placement/WhereKeywordLowerInFileNoExports.hs view
@@ -0,0 +1,15 @@+module Asdf + + + where+++f :: Int64 -> Int64+f = id'+ where id' = id++g :: Int -> Int +g = id++where' :: Int -> Int +where' = id
− test/data/multi/dist-newstyle/build/x86_64-osx/ghc-8.10.2/a-1.0.0/build/autogen/Paths_a.hs
@@ -1,50 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE NoRebindableSyntax #-}-{-# OPTIONS_GHC -fno-warn-missing-import-lists #-}-module Paths_a (- version,- getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir,- getDataFileName, getSysconfDir- ) where--import qualified Control.Exception as Exception-import Data.Version (Version(..))-import System.Environment (getEnv)-import Prelude--#if defined(VERSION_base)--#if MIN_VERSION_base(4,0,0)-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#else-catchIO :: IO a -> (Exception.Exception -> IO a) -> IO a-#endif--#else-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#endif-catchIO = Exception.catch--version :: Version-version = Version [1,0,0] []-bindir, libdir, dynlibdir, datadir, libexecdir, sysconfdir :: FilePath--bindir = "/Users/pepeiborra/.cabal/bin"-libdir = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.2/a-1.0.0-inplace"-dynlibdir = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.2"-datadir = "/Users/pepeiborra/.cabal/share/x86_64-osx-ghc-8.10.2/a-1.0.0"-libexecdir = "/Users/pepeiborra/.cabal/libexec/x86_64-osx-ghc-8.10.2/a-1.0.0"-sysconfdir = "/Users/pepeiborra/.cabal/etc"--getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir, getSysconfDir :: IO FilePath-getBinDir = catchIO (getEnv "a_bindir") (\_ -> return bindir)-getLibDir = catchIO (getEnv "a_libdir") (\_ -> return libdir)-getDynLibDir = catchIO (getEnv "a_dynlibdir") (\_ -> return dynlibdir)-getDataDir = catchIO (getEnv "a_datadir") (\_ -> return datadir)-getLibexecDir = catchIO (getEnv "a_libexecdir") (\_ -> return libexecdir)-getSysconfDir = catchIO (getEnv "a_sysconfdir") (\_ -> return sysconfdir)--getDataFileName :: FilePath -> IO FilePath-getDataFileName name = do- dir <- getDataDir- return (dir ++ "/" ++ name)
− test/data/multi/dist-newstyle/build/x86_64-osx/ghc-8.10.3/a-1.0.0/build/autogen/Paths_a.hs
@@ -1,50 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE NoRebindableSyntax #-}-{-# OPTIONS_GHC -fno-warn-missing-import-lists #-}-module Paths_a (- version,- getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir,- getDataFileName, getSysconfDir- ) where--import qualified Control.Exception as Exception-import Data.Version (Version(..))-import System.Environment (getEnv)-import Prelude--#if defined(VERSION_base)--#if MIN_VERSION_base(4,0,0)-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#else-catchIO :: IO a -> (Exception.Exception -> IO a) -> IO a-#endif--#else-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#endif-catchIO = Exception.catch--version :: Version-version = Version [1,0,0] []-bindir, libdir, dynlibdir, datadir, libexecdir, sysconfdir :: FilePath--bindir = "/Users/pepeiborra/.cabal/bin"-libdir = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.3/a-1.0.0-inplace"-dynlibdir = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.3"-datadir = "/Users/pepeiborra/.cabal/share/x86_64-osx-ghc-8.10.3/a-1.0.0"-libexecdir = "/Users/pepeiborra/.cabal/libexec/x86_64-osx-ghc-8.10.3/a-1.0.0"-sysconfdir = "/Users/pepeiborra/.cabal/etc"--getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir, getSysconfDir :: IO FilePath-getBinDir = catchIO (getEnv "a_bindir") (\_ -> return bindir)-getLibDir = catchIO (getEnv "a_libdir") (\_ -> return libdir)-getDynLibDir = catchIO (getEnv "a_dynlibdir") (\_ -> return dynlibdir)-getDataDir = catchIO (getEnv "a_datadir") (\_ -> return datadir)-getLibexecDir = catchIO (getEnv "a_libexecdir") (\_ -> return libexecdir)-getSysconfDir = catchIO (getEnv "a_sysconfdir") (\_ -> return sysconfdir)--getDataFileName :: FilePath -> IO FilePath-getDataFileName name = do- dir <- getDataDir- return (dir ++ "/" ++ name)
− test/data/multi/dist-newstyle/build/x86_64-osx/ghc-8.10.3/b-1.0.0/build/autogen/Paths_b.hs
@@ -1,50 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE NoRebindableSyntax #-}-{-# OPTIONS_GHC -fno-warn-missing-import-lists #-}-module Paths_b (- version,- getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir,- getDataFileName, getSysconfDir- ) where--import qualified Control.Exception as Exception-import Data.Version (Version(..))-import System.Environment (getEnv)-import Prelude--#if defined(VERSION_base)--#if MIN_VERSION_base(4,0,0)-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#else-catchIO :: IO a -> (Exception.Exception -> IO a) -> IO a-#endif--#else-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#endif-catchIO = Exception.catch--version :: Version-version = Version [1,0,0] []-bindir, libdir, dynlibdir, datadir, libexecdir, sysconfdir :: FilePath--bindir = "/Users/pepeiborra/.cabal/bin"-libdir = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.3/b-1.0.0-inplace"-dynlibdir = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.3"-datadir = "/Users/pepeiborra/.cabal/share/x86_64-osx-ghc-8.10.3/b-1.0.0"-libexecdir = "/Users/pepeiborra/.cabal/libexec/x86_64-osx-ghc-8.10.3/b-1.0.0"-sysconfdir = "/Users/pepeiborra/.cabal/etc"--getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir, getSysconfDir :: IO FilePath-getBinDir = catchIO (getEnv "b_bindir") (\_ -> return bindir)-getLibDir = catchIO (getEnv "b_libdir") (\_ -> return libdir)-getDynLibDir = catchIO (getEnv "b_dynlibdir") (\_ -> return dynlibdir)-getDataDir = catchIO (getEnv "b_datadir") (\_ -> return datadir)-getLibexecDir = catchIO (getEnv "b_libexecdir") (\_ -> return libexecdir)-getSysconfDir = catchIO (getEnv "b_sysconfdir") (\_ -> return sysconfdir)--getDataFileName :: FilePath -> IO FilePath-getDataFileName name = do- dir <- getDataDir- return (dir ++ "/" ++ name)
test/data/plugin-knownnat/cabal.project view
@@ -1,1 +1,4 @@ packages: .++-- Needed for ghc >= 9.0.2 and ghc-typelits-natnormalise == 0.7.6+allow-newer: ghc-typelits-natnormalise:ghc-bignum
+ test/data/symlink/hie.yaml view
@@ -0,0 +1,10 @@++cradle:+ direct:+ arguments:+ - -i+ - -isrc+ - -iother_loc/+ - other_loc/Sym.hs+ - src/Foo.hs+ - -Wall
+ test/data/symlink/some_loc/Sym.hs view
@@ -0,0 +1,4 @@+module Sym where++foo :: String+foo = ""
+ test/data/symlink/src/Foo.hs view
@@ -0,0 +1,4 @@+module Foo where++import Sym+
+ test/exe/HieDbRetry.hs view
@@ -0,0 +1,127 @@+{-# LANGUAGE MultiWayIf #-}+module HieDbRetry (tests) where++import Control.Concurrent.Extra (Var, modifyVar, newVar, readVar,+ withVar)+import Control.Exception (ErrorCall (ErrorCall), evaluate,+ throwIO, tryJust)+import Data.Text (Text)+import Data.Tuple.Extra (dupe)+import qualified Database.SQLite.Simple as SQLite+import Development.IDE.Session (retryOnException,+ retryOnSqliteBusy)+import Development.IDE.Types.Logger (Logger (Logger), Priority,+ noLogging)+import qualified System.Random as Random+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit (assertFailure, testCase, (@?=))++makeLogger :: Var [(Priority, Text)] -> Logger+makeLogger msgsVar = Logger $ \priority msg -> modifyVar msgsVar (\msgs -> pure ((priority, msg) : msgs, ()))++rng :: Random.StdGen+rng = Random.mkStdGen 0++retryOnSqliteBusyForTest :: Logger -> Int -> IO a -> IO a+retryOnSqliteBusyForTest logger maxRetryCount = retryOnException isErrorBusy logger 1 1 maxRetryCount rng++isErrorBusy :: SQLite.SQLError -> Maybe SQLite.SQLError+isErrorBusy e+ | SQLite.SQLError { sqlError = SQLite.ErrorBusy } <- e = Just e+ | otherwise = Nothing++errorBusy :: SQLite.SQLError+errorBusy = SQLite.SQLError{ sqlError = SQLite.ErrorBusy, sqlErrorDetails = "", sqlErrorContext = "" }++isErrorCall :: ErrorCall -> Maybe ErrorCall+isErrorCall e+ | ErrorCall _ <- e = Just e+ | otherwise = Nothing++tests :: TestTree+tests = testGroup "RetryHieDb"+ [ testCase "retryOnException throws exception after max retries" $ do+ logMsgsVar <- newVar []+ let logger = makeLogger logMsgsVar+ let maxRetryCount = 1++ result <- tryJust isErrorBusy (retryOnSqliteBusyForTest logger maxRetryCount (throwIO errorBusy))++ case result of+ Left exception -> do+ exception @?= errorBusy+ withVar logMsgsVar $ \logMsgs ->+ length logMsgs @?= 2+ -- uncomment if want to compare log msgs+ -- logMsgs @?= []+ Right _ -> assertFailure "Expected ErrorBusy exception"++ , testCase "retryOnException doesn't throw if given function doesn't throw" $ do+ let expected = 1 :: Int+ let maxRetryCount = 0++ actual <- retryOnSqliteBusyForTest noLogging maxRetryCount (pure expected)++ actual @?= expected++ , testCase "retryOnException retries the number of times it should" $ do+ countVar <- newVar 0+ let maxRetryCount = 3+ let incrementThenThrow = modifyVar countVar (\count -> pure (dupe (count + 1))) >> throwIO errorBusy++ _ <- tryJust isErrorBusy (retryOnSqliteBusyForTest noLogging maxRetryCount incrementThenThrow)++ withVar countVar $ \count ->+ count @?= maxRetryCount + 1++ , testCase "retryOnException doesn't retry if exception is not ErrorBusy" $ do+ countVar <- newVar (0 :: Int)+ let maxRetryCount = 1++ let throwThenIncrement = do+ count <- readVar countVar+ if count == 0 then+ evaluate (error "dummy exception")+ else+ modifyVar countVar (\count -> pure (dupe (count + 1)))+++ _ <- tryJust isErrorCall (retryOnSqliteBusyForTest noLogging maxRetryCount throwThenIncrement)++ withVar countVar $ \count ->+ count @?= 0++ , testCase "retryOnSqliteBusy retries on ErrorBusy" $ do+ countVar <- newVar (0 :: Int)++ let incrementThenThrowThenIncrement = do+ count <- readVar countVar+ if count == 0 then+ modifyVar countVar (\count -> pure (dupe (count + 1))) >> throwIO errorBusy+ else+ modifyVar countVar (\count -> pure (dupe (count + 1)))++ _ <- retryOnSqliteBusy noLogging rng incrementThenThrowThenIncrement++ withVar countVar $ \count ->+ count @?= 2++ , testCase "retryOnException exponentially backs off" $ do+ logMsgsVar <- newVar ([] :: [(Priority, Text)])++ let maxDelay = 100+ let baseDelay = 1+ let maxRetryCount = 6+ let logger = makeLogger logMsgsVar++ result <- tryJust isErrorBusy (retryOnException isErrorBusy logger maxDelay baseDelay maxRetryCount rng (throwIO errorBusy))++ case result of+ Left _ -> do+ withVar logMsgsVar $ \logMsgs ->+ if | ((_, lastLogMsg) : _) <- logMsgs ->+ -- uses log messages to indirectly check backoff...+ lastLogMsg @?= "Retries exhausted - base delay: 64, maximumDelay: 100, maxRetryCount: 0, exception: SQLite3 returned ErrorBusy while attempting to perform : "+ | otherwise -> assertFailure "Expected more than 0 log messages"+ Right _ -> assertFailure "Expected ErrorBusy exception"+ ]
test/exe/Main.hs view
@@ -7,6 +7,7 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE ImplicitParams #-}+{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE TypeOperators #-}@@ -15,7 +16,9 @@ module Main (main) where import Control.Applicative.Combinators-import Control.Exception (bracket_, catch)+import Control.Concurrent+import Control.Exception (bracket_, catch,+ finally) import qualified Control.Lens as Lens import Control.Monad import Control.Monad.IO.Class (MonadIO, liftIO)@@ -42,6 +45,7 @@ import Development.IDE.Spans.Common import Development.IDE.Test (Cursor, canonicalizeUri,+ configureCheckProject, diagnostic, expectCurrentDiagnostics, expectDiagnostics,@@ -49,15 +53,15 @@ expectMessages, expectNoMoreDiagnostics, flushMessages,- standardizeQuotes, getInterfaceFilesDir,- waitForAction, getStoredKeys,- waitForTypecheck, waitForGC)+ standardizeQuotes,+ waitForAction,+ waitForGC,+ waitForTypecheck) import Development.IDE.Test.Runfiles import qualified Development.IDE.Types.Diagnostics as Diagnostics import Development.IDE.Types.Location-import qualified Language.LSP.Types.Lens as Lens (label) import Development.Shake (getDirectoryFilesIO) import qualified Experiments as Bench import Ide.Plugin.Config@@ -68,6 +72,7 @@ SemanticTokensEdit (_start), mkRange) import Language.LSP.Types.Capabilities+import qualified Language.LSP.Types.Lens as Lens (label) import qualified Language.LSP.Types.Lens as Lsp (diagnostics, message, params)@@ -80,16 +85,15 @@ import System.FilePath import System.IO.Extra hiding (withTempDir) import qualified System.IO.Extra-import System.Info.Extra (isWindows)+import System.Info.Extra (isMac, isWindows) import System.Mem (performGC) import System.Process.Extra (CreateProcess (cwd), createPipe, proc, readCreateProcessWithExitCode) import Test.QuickCheck -- import Test.QuickCheck.Instances ()-import Control.Concurrent (threadDelay) import Control.Concurrent.Async-import Control.Lens ((^.))+import Control.Lens (to, (^.)) import Control.Monad.Extra (whenJust) import Data.IORef import Data.IORef.Extra (atomicModifyIORef_)@@ -101,6 +105,7 @@ import Development.IDE.Plugin.Test (TestRequest (BlockSeconds), WaitForIdeRuleResult (..), blockCommandId)+import qualified HieDbRetry import Ide.PluginUtils (pluginDescToIdePlugins) import Ide.Types import qualified Language.LSP.Types as LSP@@ -164,6 +169,7 @@ , pluginParsedResultTests , preprocessorTests , thTests+ , symlinkTests , safeTests , unitTests , haddockTests@@ -181,6 +187,7 @@ , codeActionHelperFunctionTests , referenceTests , garbageCollectionTests+ , HieDbRetry.tests ] initializeResponseTests :: TestTree@@ -427,10 +434,7 @@ liftIO $ writeFile (path </> "hie.yaml") cradle _ <- createDoc "ModuleD.hs" "haskell" contentD expectDiagnostics- [ ( "ModuleA.hs"- , [(DsError, (1, 7), "Cyclic module dependency between ModuleA, ModuleB")]- )- , ( "ModuleB.hs"+ [ ( "ModuleB.hs" , [(DsError, (1, 7), "Cyclic module dependency between ModuleA, ModuleB")] ) ]@@ -589,7 +593,7 @@ ] _ <- createDoc "Foo.hs" "haskell" fooContent if ghcVersion >= GHC90 then- -- Haddock parse errors are ignored on ghc-9.0.1+ -- Haddock parse errors are ignored on ghc-9.0 pure () else expectDiagnostics@@ -780,6 +784,7 @@ , suggestHideShadowTests , suggestImportDisambiguationTests , fixConstructorImportTests+ , fixModuleImportTypoTests , importRenameActionTests , fillTypedHoleTests , addSigActionTests@@ -857,28 +862,203 @@ insertImportTests :: TestTree insertImportTests = testGroup "insert import"- [ checkImport "above comment at top of module" "CommentAtTop.hs" "CommentAtTop.expected.hs" "import Data.Monoid"- , checkImport "above multiple comments below" "CommentAtTopMultipleComments.hs" "CommentAtTopMultipleComments.expected.hs" "import Data.Monoid"- , checkImport "above curly brace comment" "CommentCurlyBraceAtTop.hs" "CommentCurlyBraceAtTop.expected.hs" "import Data.Monoid"- , checkImport "above multi-line comment" "MultiLineCommentAtTop.hs" "MultiLineCommentAtTop.expected.hs" "import Data.Monoid"- , checkImport "above comment with no module explicit exports" "NoExplicitExportCommentAtTop.hs" "NoExplicitExportCommentAtTop.expected.hs" "import Data.Monoid"- , checkImport "above two-dash comment with no pipe" "TwoDashOnlyComment.hs" "TwoDashOnlyComment.expected.hs" "import Data.Monoid"- , checkImport "above comment with no (module .. where) decl" "NoModuleDeclarationCommentAtTop.hs" "NoModuleDeclarationCommentAtTop.expected.hs" "import Data.Monoid"- , checkImport "comment not at top with no (module .. where) decl" "NoModuleDeclaration.hs" "NoModuleDeclaration.expected.hs" "import Data.Monoid"- , checkImport "comment not at top (data dec is)" "DataAtTop.hs" "DataAtTop.expected.hs" "import Data.Monoid"- , checkImport "comment not at top (newtype is)" "NewTypeAtTop.hs" "NewTypeAtTop.expected.hs" "import Data.Monoid"- , checkImport "with no explicit module exports" "NoExplicitExports.hs" "NoExplicitExports.expected.hs" "import Data.Monoid"- , checkImport "add to correctly placed exisiting import" "ImportAtTop.hs" "ImportAtTop.expected.hs" "import Data.Monoid"- , checkImport "add to multiple correctly placed exisiting imports" "MultipleImportsAtTop.hs" "MultipleImportsAtTop.expected.hs" "import Data.Monoid"- , checkImport "with language pragma at top of module" "LangPragmaModuleAtTop.hs" "LangPragmaModuleAtTop.expected.hs" "import Data.Monoid"- , checkImport "with language pragma and explicit module exports" "LangPragmaModuleWithComment.hs" "LangPragmaModuleWithComment.expected.hs" "import Data.Monoid"- , checkImport "with language pragma at top and no module declaration" "LanguagePragmaAtTop.hs" "LanguagePragmaAtTop.expected.hs" "import Data.Monoid"- , checkImport "with multiple lang pragmas and no module declaration" "MultipleLanguagePragmasNoModuleDeclaration.hs" "MultipleLanguagePragmasNoModuleDeclaration.expected.hs" "import Data.Monoid"- , checkImport "with pragmas and shebangs" "LanguagePragmasThenShebangs.hs" "LanguagePragmasThenShebangs.expected.hs" "import Data.Monoid"- , checkImport "with pragmas and shebangs but no comment at top" "PragmasAndShebangsNoComment.hs" "PragmasAndShebangsNoComment.expected.hs" "import Data.Monoid"- , checkImport "module decl no exports under pragmas and shebangs" "PragmasShebangsAndModuleDecl.hs" "PragmasShebangsAndModuleDecl.expected.hs" "import Data.Monoid"- , checkImport "module decl with explicit import under pragmas and shebangs" "PragmasShebangsModuleExplicitExports.hs" "PragmasShebangsModuleExplicitExports.expected.hs" "import Data.Monoid"- , checkImport "module decl and multiple imports" "ModuleDeclAndImports.hs" "ModuleDeclAndImports.expected.hs" "import Data.Monoid"+ [ expectFailBecause+ ("'findPositionFromImportsOrModuleDecl' function adds import directly under line with module declaration, "+ ++ "not accounting for case when 'where' keyword is placed on lower line")+ (checkImport+ "module where keyword lower in file no exports"+ "WhereKeywordLowerInFileNoExports.hs"+ "WhereKeywordLowerInFileNoExports.expected.hs"+ "import Data.Int")+ , expectFailBecause+ ("'findPositionFromImportsOrModuleDecl' function adds import directly under line with module exports list, "+ ++ "not accounting for case when 'where' keyword is placed on lower line")+ (checkImport+ "module where keyword lower in file with exports"+ "WhereDeclLowerInFile.hs"+ "WhereDeclLowerInFile.expected.hs"+ "import Data.Int")+ , expectFailBecause+ "'findNextPragmaPosition' function doesn't account for case when shebang is not placed at top of file"+ (checkImport+ "Shebang not at top with spaces"+ "ShebangNotAtTopWithSpaces.hs"+ "ShebangNotAtTopWithSpaces.expected.hs"+ "import Data.Monoid")+ , expectFailBecause+ "'findNextPragmaPosition' function doesn't account for case when shebang is not placed at top of file"+ (checkImport+ "Shebang not at top no space"+ "ShebangNotAtTopNoSpace.hs"+ "ShebangNotAtTopNoSpace.expected.hs"+ "import Data.Monoid")+ , expectFailBecause+ ("'findNextPragmaPosition' function doesn't account for case "+ ++ "when OPTIONS_GHC pragma is not placed at top of file")+ (checkImport+ "OPTIONS_GHC pragma not at top with spaces"+ "OptionsNotAtTopWithSpaces.hs"+ "OptionsNotAtTopWithSpaces.expected.hs"+ "import Data.Monoid")+ , expectFailBecause+ ("'findNextPragmaPosition' function doesn't account for "+ ++ "case when shebang is not placed at top of file")+ (checkImport+ "Shebang not at top of file"+ "ShebangNotAtTop.hs"+ "ShebangNotAtTop.expected.hs"+ "import Data.Monoid")+ , expectFailBecause+ ("'findNextPragmaPosition' function doesn't account for case "+ ++ "when OPTIONS_GHC is not placed at top of file")+ (checkImport+ "OPTIONS_GHC pragma not at top of file"+ "OptionsPragmaNotAtTop.hs"+ "OptionsPragmaNotAtTop.expected.hs"+ "import Data.Monoid")+ , expectFailBecause+ ("'findNextPragmaPosition' function doesn't account for case when "+ ++ "OPTIONS_GHC pragma is not placed at top of file")+ (checkImport+ "pragma not at top with comment at top"+ "PragmaNotAtTopWithCommentsAtTop.hs"+ "PragmaNotAtTopWithCommentsAtTop.expected.hs"+ "import Data.Monoid")+ , expectFailBecause+ ("'findNextPragmaPosition' function doesn't account for case when "+ ++ "OPTIONS_GHC pragma is not placed at top of file")+ (checkImport+ "pragma not at top multiple comments"+ "PragmaNotAtTopMultipleComments.hs"+ "PragmaNotAtTopMultipleComments.expected.hs"+ "import Data.Monoid")+ , expectFailBecause+ "'findNextPragmaPosition' function doesn't account for case of multiline pragmas"+ (checkImport+ "after multiline language pragmas"+ "MultiLinePragma.hs"+ "MultiLinePragma.expected.hs"+ "import Data.Monoid")+ , checkImport+ "pragmas not at top with module declaration"+ "PragmaNotAtTopWithModuleDecl.hs"+ "PragmaNotAtTopWithModuleDecl.expected.hs"+ "import Data.Monoid"+ , checkImport+ "pragmas not at top with imports"+ "PragmaNotAtTopWithImports.hs"+ "PragmaNotAtTopWithImports.expected.hs"+ "import Data.Monoid"+ , checkImport+ "above comment at top of module"+ "CommentAtTop.hs"+ "CommentAtTop.expected.hs"+ "import Data.Monoid"+ , checkImport+ "above multiple comments below"+ "CommentAtTopMultipleComments.hs"+ "CommentAtTopMultipleComments.expected.hs"+ "import Data.Monoid"+ , checkImport+ "above curly brace comment"+ "CommentCurlyBraceAtTop.hs"+ "CommentCurlyBraceAtTop.expected.hs"+ "import Data.Monoid"+ , checkImport+ "above multi-line comment"+ "MultiLineCommentAtTop.hs"+ "MultiLineCommentAtTop.expected.hs"+ "import Data.Monoid"+ , checkImport+ "above comment with no module explicit exports"+ "NoExplicitExportCommentAtTop.hs"+ "NoExplicitExportCommentAtTop.expected.hs"+ "import Data.Monoid"+ , checkImport+ "above two-dash comment with no pipe"+ "TwoDashOnlyComment.hs"+ "TwoDashOnlyComment.expected.hs"+ "import Data.Monoid"+ , checkImport+ "above comment with no (module .. where) decl"+ "NoModuleDeclarationCommentAtTop.hs"+ "NoModuleDeclarationCommentAtTop.expected.hs"+ "import Data.Monoid"+ , checkImport+ "comment not at top with no (module .. where) decl"+ "NoModuleDeclaration.hs"+ "NoModuleDeclaration.expected.hs"+ "import Data.Monoid"+ , checkImport+ "comment not at top (data dec is)"+ "DataAtTop.hs"+ "DataAtTop.expected.hs"+ "import Data.Monoid"+ , checkImport+ "comment not at top (newtype is)"+ "NewTypeAtTop.hs"+ "NewTypeAtTop.expected.hs"+ "import Data.Monoid"+ , checkImport+ "with no explicit module exports"+ "NoExplicitExports.hs"+ "NoExplicitExports.expected.hs"+ "import Data.Monoid"+ , checkImport+ "add to correctly placed exisiting import"+ "ImportAtTop.hs"+ "ImportAtTop.expected.hs"+ "import Data.Monoid"+ , checkImport+ "add to multiple correctly placed exisiting imports"+ "MultipleImportsAtTop.hs"+ "MultipleImportsAtTop.expected.hs"+ "import Data.Monoid"+ , checkImport+ "with language pragma at top of module"+ "LangPragmaModuleAtTop.hs"+ "LangPragmaModuleAtTop.expected.hs"+ "import Data.Monoid"+ , checkImport+ "with language pragma and explicit module exports"+ "LangPragmaModuleWithComment.hs"+ "LangPragmaModuleWithComment.expected.hs"+ "import Data.Monoid"+ , checkImport+ "with language pragma at top and no module declaration"+ "LanguagePragmaAtTop.hs"+ "LanguagePragmaAtTop.expected.hs"+ "import Data.Monoid"+ , checkImport+ "with multiple lang pragmas and no module declaration"+ "MultipleLanguagePragmasNoModuleDeclaration.hs"+ "MultipleLanguagePragmasNoModuleDeclaration.expected.hs"+ "import Data.Monoid"+ , checkImport+ "with pragmas and shebangs"+ "LanguagePragmasThenShebangs.hs"+ "LanguagePragmasThenShebangs.expected.hs"+ "import Data.Monoid"+ , checkImport+ "with pragmas and shebangs but no comment at top"+ "PragmasAndShebangsNoComment.hs"+ "PragmasAndShebangsNoComment.expected.hs"+ "import Data.Monoid"+ , checkImport+ "module decl no exports under pragmas and shebangs"+ "PragmasShebangsAndModuleDecl.hs"+ "PragmasShebangsAndModuleDecl.expected.hs"+ "import Data.Monoid"+ , checkImport+ "module decl with explicit import under pragmas and shebangs"+ "PragmasShebangsModuleExplicitExports.hs"+ "PragmasShebangsModuleExplicitExports.expected.hs"+ "import Data.Monoid"+ , checkImport+ "module decl and multiple imports"+ "ModuleDeclAndImports.hs"+ "ModuleDeclAndImports.expected.hs"+ "import Data.Monoid" ] checkImport :: String -> FilePath -> FilePath -> T.Text -> TestTree@@ -1103,19 +1283,20 @@ , "stuffB :: Integer" , "stuffB = 123" , "stuffC = ()"+ , "_stuffD = '_'" ] _docA <- createDoc "ModuleA.hs" "haskell" contentA let contentB = T.unlines [ "{-# OPTIONS_GHC -Wunused-imports #-}" , "module ModuleB where"- , "import ModuleA (stuffA, stuffB, stuffC, stuffA)"+ , "import ModuleA (stuffA, stuffB, _stuffD, stuffC, stuffA)" , "main = print stuffB" ] docB <- createDoc "ModuleB.hs" "haskell" contentB _ <- waitForDiagnostics [InR action@CodeAction { _title = actionTitle }, _] <- getCodeActions docB (Range (Position 2 0) (Position 2 5))- liftIO $ "Remove stuffA, stuffC from import" @=? actionTitle+ liftIO $ "Remove _stuffD, stuffA, stuffC from import" @=? actionTitle executeCodeAction action contentAfterAction <- documentContents docB let expectedContentAfterAction = T.unlines@@ -1560,7 +1741,8 @@ , "import A (pattern Some)" , "k (Some x) = x" ])- , testSession "type constructor name same as data constructor name" $ template+ , ignoreForGHC92 "Diagnostic message has no suggestions" $+ testSession "type constructor name same as data constructor name" $ template [("ModuleA.hs", T.unlines [ "module ModuleA where" , "newtype Foo = Foo Int"@@ -1603,10 +1785,7 @@ codeActionTitle CodeAction{_title=x} = x template setUpModules moduleUnderTest range expectedTitles expectedContentB = do- sendNotification SWorkspaceDidChangeConfiguration- (DidChangeConfigurationParams $ toJSON- def{checkProject = overrideCheckProject})-+ configureCheckProject overrideCheckProject mapM_ (\x -> createDoc (fst x) "haskell" (snd x)) setUpModules docB <- createDoc (fst moduleUnderTest) "haskell" (snd moduleUnderTest)@@ -1633,6 +1812,31 @@ contentAfterAction <- documentContents docB liftIO $ expectedContentB @=? contentAfterAction +fixModuleImportTypoTests :: TestTree+fixModuleImportTypoTests = testGroup "fix module import typo"+ [ testSession "works when single module suggested" $ do+ doc <- createDoc "A.hs" "haskell" "import Data.Cha"+ _ <- waitForDiagnostics+ InR action@CodeAction { _title = actionTitle } : _ <- getCodeActions doc (R 0 0 0 10)+ liftIO $ actionTitle @?= "replace with Data.Char"+ executeCodeAction action+ contentAfterAction <- documentContents doc+ liftIO $ contentAfterAction @?= "import Data.Char"+ , testSession "works when multiple modules suggested" $ do+ doc <- createDoc "A.hs" "haskell" "import Data.I"+ _ <- waitForDiagnostics+ actions <- sortOn (\(InR CodeAction{_title=x}) -> x) <$> getCodeActions doc (R 0 0 0 10)+ let actionTitles = [ title | InR CodeAction{_title=title} <- actions ]+ liftIO $ actionTitles @?= [ "replace with Data.Eq"+ , "replace with Data.Int"+ , "replace with Data.Ix"+ ]+ let InR replaceWithDataEq : _ = actions+ executeCodeAction replaceWithDataEq+ contentAfterAction <- documentContents doc+ liftIO $ contentAfterAction @?= "import Data.Eq"+ ]+ extendImportTestsRegEx :: TestTree extendImportTestsRegEx = testGroup "regex parsing" [@@ -1783,6 +1987,7 @@ test = test' False wantWait = test' True True test' waitForCheckProject wanted imps def other newImp = testSessionWithExtraFiles "hover" (T.unpack def) $ \dir -> do+ configureCheckProject waitForCheckProject let before = T.unlines $ "module A where" : ["import " <> x | x <- imps] ++ def : other after = T.unlines $ "module A where" : ["import " <> x | x <- imps] ++ [newImp] ++ def : other cradle = "cradle: {direct: {arguments: [-hide-all-packages, -package, base, -package, text, -package-env, -, A, Bar, Foo, B]}}"@@ -1793,8 +1998,8 @@ _diags <- waitForDiagnostics -- there isn't a good way to wait until the whole project is checked atm when waitForCheckProject $ liftIO $ sleep 0.5- let defLine = length imps + 1- range = Range (Position defLine 0) (Position defLine maxBoundUinteger)+ let defLine = fromIntegral $ length imps + 1+ range = Range (Position defLine 0) (Position defLine maxBound) actions <- getCodeActions doc range if wanted then do@@ -2107,7 +2312,7 @@ doc <- createDoc "A.hs" "haskell" $ T.unlines (header <> origin) void waitForDiagnostics waitForProgressDone- cas <- getCodeActions doc (Range (Position (line1 + length header) col1) (Position (line2 + length header) col2))+ cas <- getCodeActions doc (Range (Position (fromIntegral $ line1 + length header) col1) (Position (fromIntegral $ line2 + length header) col2)) void $ k [x | x@(InR ca) <- cas, "Hide" `T.isPrefixOf` (ca ^. L.title)] contentAfter <- documentContents doc liftIO $ contentAfter @?= T.unlines (header <> expected)@@ -2326,9 +2531,9 @@ return (action, actionTitle) addTypeAnnotationsToLiteralsTest :: TestTree-addTypeAnnotationsToLiteralsTest = testGroup "add type annotations to literals to satisfy contraints"+addTypeAnnotationsToLiteralsTest = testGroup "add type annotations to literals to satisfy constraints" [- testSession "add default type to satisfy one contraint" $+ testSession "add default type to satisfy one constraint" $ testFor (T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}" , "module A (f) where"@@ -2343,7 +2548,7 @@ , "f = (1 :: Integer)" ]) - , testSession "add default type to satisfy one contraint in nested expressions" $+ , testSession "add default type to satisfy one constraint in nested expressions" $ testFor (T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}" , "module A where"@@ -2361,7 +2566,7 @@ , " let x = (3 :: Integer)" , " in x" ])- , testSession "add default type to satisfy one contraint in more nested expressions" $+ , testSession "add default type to satisfy one constraint in more nested expressions" $ testFor (T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}" , "module A where"@@ -2379,7 +2584,7 @@ , " let x = let y = (5 :: Integer) in y" , " in x" ])- , testSession "add default type to satisfy one contraint with duplicate literals" $+ , testSession "add default type to satisfy one constraint with duplicate literals" $ testFor (T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}" , "{-# LANGUAGE OverloadedStrings #-}"@@ -2401,7 +2606,8 @@ , "" , "f = seq (\"debug\" :: " <> listOfChar <> ") traceShow \"debug\"" ])- , testSession "add default type to satisfy two contraints" $+ , knownBrokenForGhcVersions [GHC92] "GHC 9.2 only has 'traceShow' in error span" $+ testSession "add default type to satisfy two constraints" $ testFor (T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}" , "{-# LANGUAGE OverloadedStrings #-}"@@ -2421,7 +2627,8 @@ , "" , "f a = traceShow (\"debug\" :: " <> listOfChar <> ") a" ])- , testSession "add default type to satisfy two contraints with duplicate literals" $+ , knownBrokenForGhcVersions [GHC92] "GHC 9.2 only has 'traceShow' in error span" $+ testSession "add default type to satisfy two constraints with duplicate literals" $ testFor (T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}" , "{-# LANGUAGE OverloadedStrings #-}"@@ -2542,7 +2749,7 @@ let expectedCode = sourceCode newA newB newC doc <- createDoc "Testing.hs" "haskell" originalCode _ <- waitForDiagnostics- actionsOrCommands <- getCodeActions doc (Range (Position 9 0) (Position 9 maxBoundUinteger))+ actionsOrCommands <- getCodeActions doc (Range (Position 9 0) (Position 9 maxBound)) chosenAction <- liftIO $ pickActionWithTitle actionTitle actionsOrCommands executeCodeAction chosenAction modifiedCode <- documentContents doc@@ -2583,7 +2790,7 @@ , "ioToSome = " <> x ] doc <- createDoc "Test.hs" "haskell" $ mkDoc "_toException" _ <- waitForDiagnostics- actions <- getCodeActions doc (Range (Position 3 0) (Position 3 maxBoundUinteger))+ actions <- getCodeActions doc (Range (Position 3 0) (Position 3 maxBound)) chosen <- liftIO $ pickActionWithTitle "replace _toException with E.toException" actions executeCodeAction chosen modifiedCode <- documentContents doc@@ -3078,7 +3285,7 @@ let expectedCode = after' def sig doc <- createDoc "Sigs.hs" "haskell" originalCode _ <- waitForDiagnostics- actionsOrCommands <- getCodeActions doc (Range (Position 5 1) (Position 5 maxBoundUinteger))+ actionsOrCommands <- getCodeActions doc (Range (Position 5 1) (Position 5 maxBound)) chosenAction <- liftIO $ pickActionWithTitle ("add signature: " <> sig) actionsOrCommands executeCodeAction chosenAction modifiedCode <- documentContents doc@@ -3100,7 +3307,7 @@ , "pattern Point{x, y} <- (x, y)\n where Point x y = (x, y)" >:: "pattern Point :: a -> b -> (a, b)" , "pattern MkT1' b = MkT1 42 b" >:: "pattern MkT1' :: (Eq a, Num a) => Show b => b -> T1 a" , "pattern MkT1' b <- MkT1 42 b" >:: "pattern MkT1' :: (Eq a, Num a) => Show b => b -> T1 a"- , "pattern MkT1' b <- MkT1 42 b\n where MkT1' b = T1 42 b" >:: "pattern MkT1' :: (Eq a, Num a) => Show b => b -> T1 a"+ , "pattern MkT1' b <- MkT1 42 b\n where MkT1' b = MkT1 42 b" >:: "pattern MkT1' :: (Eq a, Num a) => Show b => b -> T1 a" ] exportUnusedTests :: TestTree@@ -3126,7 +3333,8 @@ (R 2 0 2 11) "Export ‘bar’" Nothing- , testSession "type is exported but not the constructor of same name" $ template+ , ignoreForGHC92 "Diagnostic message has no suggestions" $+ testSession "type is exported but not the constructor of same name" $ template (T.unlines [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" , "module A (Foo) where"@@ -3167,7 +3375,7 @@ "Export ‘bar’" (Just $ T.unlines [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"- , "module A (foo,bar) where"+ , "module A (foo, bar) where" , "foo = id" , "bar = foo"]) , testSession "multi line explicit exports" $ template@@ -3184,7 +3392,7 @@ [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" , "module A" , " ("- , " foo,bar) where"+ , " foo, bar) where" , "foo = id" , "bar = foo"]) , testSession "export list ends in comma" $ template@@ -3261,7 +3469,7 @@ (Just $ T.unlines [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" , "{-# LANGUAGE TypeFamilies #-}"- , "module A (Foo(..)) where"+ , "module A (Foo) where" , "type family Foo p"]) , testSession "unused typeclass" $ template (T.unlines@@ -3322,7 +3530,7 @@ [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" , "{-# LANGUAGE TypeFamilies #-}" , "{-# LANGUAGE TypeOperators #-}"- , "module A (type (:<)(..)) where"+ , "module A (type (:<)) where" , "type family (:<)"]) , testSession "typeclass operator" $ template (T.unlines@@ -3618,13 +3826,13 @@ , ("pattern Point{x, y} <- (x, y)\n where Point x y = (x, y)", "pattern Point :: a -> b -> (a, b)") , ("pattern MkT1' b = MkT1 42 b", "pattern MkT1' :: (Eq a, Num a) => Show b => b -> T1 a") , ("pattern MkT1' b <- MkT1 42 b", "pattern MkT1' :: (Eq a, Num a) => Show b => b -> T1 a")- , ("pattern MkT1' b <- MkT1 42 b\n where MkT1' b = T1 42 b", "pattern MkT1' :: (Eq a, Num a) => Show b => b -> T1 a")+ , ("pattern MkT1' b <- MkT1 42 b\n where MkT1' b = MkT1 42 b", "pattern MkT1' :: (Eq a, Num a) => Show b => b -> T1 a") , ("qualifiedSigTest= C.realPart", "qualifiedSigTest :: C.Complex a -> a") , ("head = 233", "head :: Integer") , ("rank2Test (k :: forall a . a -> a) = (k 233 :: Int, k \"QAQ\")", "rank2Test :: (forall a. a -> a) -> (Int, " <> listOfChar <> ")") , ("symbolKindTest = Proxy @\"qwq\"", "symbolKindTest :: Proxy \"qwq\"") , ("promotedKindTest = Proxy @Nothing", "promotedKindTest :: Proxy 'Nothing")- , ("typeOperatorTest = Refl", "typeOperatorTest :: a :~: a")+ , ("typeOperatorTest = Refl", if ghcVersion >= GHC92 then "typeOperatorTest :: forall {k} {a :: k}. a :~: a" else "typeOperatorTest :: a :~: a") , ("notInScopeTest = mkCharType", "notInScopeTest :: String -> Data.Data.DataType") ] in testGroup@@ -3776,14 +3984,14 @@ spaceL37 = Position 41 24 ; space = [ExpectNoDefinitions, ExpectHoverText [":: Char"]] docL41 = Position 45 1 ; doc = [ExpectHoverText ["Recognizable docs: kpqz"]] ; constr = [ExpectHoverText ["Monad m"]]- eitL40 = Position 44 28 ; kindE = [ExpectHoverText [":: * -> * -> *\n"]]- intL40 = Position 44 34 ; kindI = [ExpectHoverText [":: *\n"]]+ eitL40 = Position 44 28 ; kindE = [ExpectHoverText [if ghcVersion >= GHC92 then ":: Type -> Type -> Type\n" else ":: * -> * -> *\n"]]+ intL40 = Position 44 34 ; kindI = [ExpectHoverText [if ghcVersion >= GHC92 then ":: Type\n" else ":: *\n"]] tvrL40 = Position 44 37 ; kindV = [ExpectHoverText [":: * -> *\n"]] intL41 = Position 45 20 ; litI = [ExpectHoverText ["7518"]] chrL36 = Position 41 24 ; litC = [ExpectHoverText ["'f'"]] txtL8 = Position 12 14 ; litT = [ExpectHoverText ["\"dfgy\""]] lstL43 = Position 47 12 ; litL = [ExpectHoverText ["[8391 :: Int, 6268]"]]- outL45 = Position 49 3 ; outSig = [ExpectHoverText ["outer", "Bool"], mkR 46 0 46 5]+ outL45 = Position 49 3 ; outSig = [ExpectHoverText ["outer", "Bool"], mkR 50 0 50 5] innL48 = Position 52 5 ; innSig = [ExpectHoverText ["inner", "Char"], mkR 49 2 49 7] holeL60 = Position 62 7 ; hleInfo = [ExpectHoverText ["_ ::"]] cccL17 = Position 17 16 ; docLink = [ExpectHoverText ["[Documentation](file:///"]]@@ -3837,15 +4045,19 @@ test no yes docL41 constr "type constraint in hover info #1012" else test no broken docL41 constr "type constraint in hover info #1012"- , test broken broken outL45 outSig "top-level signature #767"+ , test no yes outL45 outSig "top-level signature #767" , test broken broken innL48 innSig "inner signature #767" , test no yes holeL60 hleInfo "hole without internal name #831" , test no skip cccL17 docLink "Haddock html links" , testM yes yes imported importedSig "Imported symbol" , testM yes yes reexported reexportedSig "Imported symbol (reexported)"- , if ghcVersion == GHC90 && isWindows then+ , if | ghcVersion == GHC90 && isWindows -> test no broken thLocL57 thLoc "TH Splice Hover"- else+ | ghcVersion == GHC92 && (isWindows || isMac) ->+ -- Some GHC 9.2 distributions ship without .hi docs+ -- https://gitlab.haskell.org/ghc/ghc/-/issues/20903+ test no broken thLocL57 thLoc "TH Splice Hover"+ | otherwise -> test no yes thLocL57 thLoc "TH Splice Hover" ] where yes, broken :: (TestTree -> Maybe TestTree)@@ -3863,6 +4075,7 @@ pluginSimpleTests :: TestTree pluginSimpleTests = ignoreInWindowsForGHC88And810 $+ ignoreForGHC92 "blocked on ghc-typelits-natnormalise" $ testSessionWithExtraFiles "plugin-knownnat" "simple plugin" $ \dir -> do _ <- openDoc (dir </> "KnownNat.hs") "haskell" liftIO $ writeFile (dir</>"hie.yaml")@@ -3877,6 +4090,7 @@ pluginParsedResultTests :: TestTree pluginParsedResultTests = ignoreInWindowsForGHC88And810 $+ ignoreForGHC92 "No need for this plugin anymore!" $ testSessionWithExtraFiles "plugin-recorddot" "parsedResultAction plugin" $ \dir -> do _ <- openDoc (dir</> "RecordDot.hs") "haskell" expectNoMoreDiagnostics 2@@ -3893,9 +4107,9 @@ "foo = 42" ] -- The error locations differ depending on which C-preprocessor is used.- -- Some give the column number and others don't (hence -1). Assert either+ -- Some give the column number and others don't (hence maxBound == -1 unsigned). Assert either -- of them.- (run $ expectError content (2, -1))+ (run $ expectError content (2, maxBound)) `catch` ( \e -> do let _ = e :: HUnitFailure run $ expectError content (2, 1)@@ -4056,6 +4270,18 @@ expectDiagnostics [ ( cPath, [(DsWarning, (3, 0), "Top-level binding with no type signature: a :: A")] ) ] ] +-- | Tests for projects that use symbolic links one way or another+symlinkTests :: TestTree+symlinkTests =+ testGroup "Projects using Symlinks"+ [ testCase "Module is symlinked" $ runWithExtraFiles "symlink" $ \dir -> do+ liftIO $ createFileLink (dir </> "some_loc" </> "Sym.hs") (dir </> "other_loc" </> "Sym.hs")+ let fooPath = dir </> "src" </> "Foo.hs"+ _ <- openDoc fooPath "haskell"+ expectDiagnosticsWithTags [("src" </> "Foo.hs", [(DsWarning, (2, 0), "The import of 'Sym' is redundant", Just DtUnnecessary)])]+ pure ()+ ]+ -- | Test that all modules have linkables thLoadingTest :: TestTree thLoadingTest = testCase "Loading linkables" $ runWithExtraFiles "THLoading" $ \dir -> do@@ -4063,7 +4289,6 @@ _ <- openDoc thb "haskell" expectNoMoreDiagnostics 1 - -- | test that TH is reevaluated on typecheck thReloadingTest :: Bool -> TestTree thReloadingTest unboxed = testCase name $ runWithExtraFiles dir $ \dir -> do@@ -4317,6 +4542,15 @@ ,("abcdefghi", CiFunction, "abcdefghi", True, False, Nothing) ], completionTest+ "type family"+ ["{-# LANGUAGE DataKinds, TypeFamilies #-}"+ ,"type family Bar a"+ ,"a :: Ba"+ ]+ (Position 2 7)+ [("Bar", CiStruct, "Bar", True, False, Nothing)+ ],+ completionTest "class method" [ "class Test a where"@@ -4771,7 +5005,7 @@ , DocumentHighlight (R 6 10 6 13) (Just HkRead) , DocumentHighlight (R 7 12 7 15) (Just HkRead) ]- , knownBrokenForGhcVersions [GHC90] "Ghc9 highlights the constructor and not just this field" $+ , knownBrokenForGhcVersions [GHC90, GHC92] "Ghc9 highlights the constructor and not just this field" $ testSessionWait "record" $ do doc <- createDoc "A.hs" "haskell" recsource _ <- waitForDiagnostics@@ -4912,7 +5146,7 @@ let source = T.unlines ["data A = B {", " x :: Int", " , y :: Int}"] docId <- createDoc "A.hs" "haskell" source symbols <- getDocumentSymbols docId- liftIO $ symbols @=? Left+ liftIO $ symbols @?= Left [ docSymbolWithChildren "A" SkStruct (R 0 0 2 13) [ docSymbolWithChildren' "B" SkConstructor (R 0 9 2 13) (R 0 9 0 10) [ docSymbol "x" SkField (R 1 2 1 3)@@ -4976,7 +5210,7 @@ SkFile Nothing Nothing- (R 0 0 maxBoundUinteger 0)+ (R 0 0 maxBound 0) loc (Just $ List cc) classSymbol name loc cc = DocumentSymbol name@@ -4988,7 +5222,7 @@ loc (Just $ List cc) -pattern R :: Int -> Int -> Int -> Int -> Range+pattern R :: UInt -> UInt -> UInt -> UInt -> Range pattern R x y x' y' = Range (Position x y) (Position x' y') xfail :: TestTree -> String -> TestTree@@ -5005,6 +5239,11 @@ ignoreInWindowsBecause "tests are unreliable in windows for ghc 8.8 and 8.10" | otherwise = id +ignoreForGHC92 :: String -> TestTree -> TestTree+ignoreForGHC92 msg+ | ghcVersion == GHC92 = ignoreTestBecause msg+ | otherwise = id+ ignoreInWindowsForGHC88 :: TestTree -> TestTree ignoreInWindowsForGHC88 | ghcVersion == GHC88 =@@ -5029,10 +5268,10 @@ -- | ExpectExtern -- TODO: as above, but expected to succeed: need some more info in here, once we have some working examples deriving Eq -mkR :: Int -> Int -> Int -> Int -> Expect+mkR :: UInt -> UInt -> UInt -> UInt -> Expect mkR startLine startColumn endLine endColumn = ExpectRange $ mkRange startLine startColumn endLine endColumn -mkL :: Uri -> Int -> Int -> Int -> Int -> Expect+mkL :: Uri -> UInt -> UInt -> UInt -> UInt -> Expect mkL uri startLine startColumn endLine endColumn = ExpectLocation $ Location uri $ mkRange startLine startColumn endLine endColumn haddockTests :: TestTree@@ -5168,7 +5407,7 @@ expectDiagnostics $ if ghcVersion >= GHC90 -- String vs [Char] causes this change in error message- then [("Foo.hs", [(DsError, (4, 6), "Couldn't match type")])]+ then [("Foo.hs", [(DsError, if ghcVersion >= GHC92 then (4,11) else (4, 6), "Couldn't match type")])] else [("Foo.hs", [(DsError, (4, 6), "Couldn't match expected type")])] -- Now modify the dependent file liftIO $ writeFile depFilePath "B"@@ -5325,6 +5564,7 @@ ifaceErrorTest :: TestTree ifaceErrorTest = testCase "iface-error-test-1" $ runWithExtraFiles "recomp" $ \dir -> do+ configureCheckProject True let bPath = dir </> "B.hs" pPath = dir </> "P.hs" @@ -5689,6 +5929,8 @@ referenceTestSession :: String -> FilePath -> [FilePath] -> (FilePath -> Session ()) -> TestTree referenceTestSession name thisDoc docs' f = testSessionWithExtraFiles "references" name $ \dir -> do+ -- needed to build whole project indexing+ configureCheckProject True let docs = map (dir </>) $ delete thisDoc $ nubOrd docs' -- Initial Index docid <- openDoc thisDoc "haskell"@@ -5716,14 +5958,14 @@ where docs = map fst3 expected -type SymbolLocation = (FilePath, Int, Int)+type SymbolLocation = (FilePath, UInt, UInt) expectSameLocations :: [Location] -> [SymbolLocation] -> Assertion expectSameLocations actual expected = do let actual' = Set.map (\location -> (location ^. L.uri- , location ^. L.range . L.start . L.line- , location ^. L.range . L.start . L.character))+ , location ^. L.range . L.start . L.line . to fromIntegral+ , location ^. L.range . L.start . L.character . to fromIntegral)) $ Set.fromList actual expected' <- Set.fromList <$> (forM expected $ \(file, l, c) -> do@@ -5769,7 +6011,7 @@ , title == actionTitle ] -mkRange :: Int -> Int -> Int -> Int -> Range+mkRange :: UInt -> UInt -> UInt -> UInt -> Range mkRange a b c d = Range (Position a b) (Position c d) run :: Session a -> IO a@@ -5819,7 +6061,9 @@ -- Only sets HOME if it wasn't already set. setEnv "HOME" "/homeless-shelter" False conf <- getConfigFromEnv- runSessionWithConfig conf cmd lspTestCaps projDir s+ runSessionWithConfig conf cmd lspTestCaps projDir $ do+ configureCheckProject False+ s getConfigFromEnv :: IO SessionConfig getConfigFromEnv = do@@ -5836,7 +6080,7 @@ convertVal _ = True lspTestCaps :: ClientCapabilities-lspTestCaps = fullCaps { _window = Just $ WindowClientCapabilities $ Just True }+lspTestCaps = fullCaps { _window = Just $ WindowClientCapabilities (Just True) Nothing Nothing } openTestDataDoc :: FilePath -> Session TextDocumentIdentifier openTestDataDoc path = do@@ -6013,9 +6257,13 @@ if t /= t' then return delay_us else findResolution_us (delay_us * 10) -testIde :: IDE.Arguments -> Session () -> IO ()-testIde arguments session = do+testIde :: IDE.Arguments -> Session a -> IO a+testIde = testIde' "."++testIde' :: FilePath -> IDE.Arguments -> Session a -> IO a+testIde' projDir arguments session = do config <- getConfigFromEnv+ cwd <- getCurrentDirectory (hInRead, hInWrite) <- createPipe (hOutRead, hOutWrite) <- createPipe let server = IDE.defaultMain arguments@@ -6023,9 +6271,11 @@ , IDE.argsHandleOut = pure hOutWrite } - withAsync server $ \_ ->- runSessionWithHandles hInWrite hOutRead config lspTestCaps "." session+ flip finally (setCurrentDirectory cwd) $ withAsync server $ \_ ->+ runSessionWithHandles hInWrite hOutRead config lspTestCaps projDir session ++ positionMappingTests :: TestTree positionMappingTests = testGroup "position mapping"@@ -6168,25 +6418,29 @@ genPosition :: Rope -> Gen Position genPosition r = do- row <- choose (0, max 0 $ rows - 1)+ let rows = Rope.rows r+ row <- choose (0, max 0 $ rows - 1) `suchThat` inBounds @UInt let columns = Rope.columns (nthLine row r)- column <- choose (0, max 0 $ columns - 1)- pure $ Position row column- where rows = Rope.rows r+ column <- choose (0, max 0 $ columns - 1) `suchThat` inBounds @UInt+ pure $ Position (fromIntegral row) (fromIntegral column) genRange :: Rope -> Gen Range genRange r = do+ let rows = Rope.rows r startPos@(Position startLine startColumn) <- genPosition r- let maxLineDiff = max 0 $ rows - 1 - startLine- endLine <- choose (startLine, startLine + maxLineDiff)- let columns = Rope.columns (nthLine endLine r)+ let maxLineDiff = max 0 $ rows - 1 - fromIntegral startLine+ endLine <- choose (fromIntegral startLine, fromIntegral startLine + maxLineDiff) `suchThat` inBounds @UInt+ let columns = Rope.columns (nthLine (fromIntegral endLine) r) endColumn <-- if startLine == endLine- then choose (startColumn, columns)+ if fromIntegral startLine == endLine+ then choose (fromIntegral startColumn, columns) else choose (0, max 0 $ columns - 1)- pure $ Range startPos (Position endLine endColumn)- where rows = Rope.rows r+ `suchThat` inBounds @UInt+ pure $ Range startPos (Position (fromIntegral endLine) (fromIntegral endColumn)) +inBounds :: forall b a . (Integral a, Integral b, Bounded b) => a -> Bool+inBounds a = let i = toInteger a in i <= toInteger (maxBound @b) && i >= toInteger (minBound @b)+ -- | Get the ith line of a rope, starting from 0. Trailing newline not included. nthLine :: Int -> Rope -> Rope nthLine i r@@ -6224,11 +6478,6 @@ | otherwise = "[Char]" -- | Ghc 9 doesn't include the $-sign in TH warnings like earlier versions did-thDollarIdx :: Int+thDollarIdx :: UInt thDollarIdx | ghcVersion >= GHC90 = 1 | otherwise = 0---- | We don't have a uinteger type yet. So hardcode the maxBound of uinteger, 2 ^ 31 - 1--- as a constant.-maxBoundUinteger :: Int-maxBoundUinteger = 2147483647
test/exe/Progress.hs view
@@ -1,7 +1,13 @@+{-# LANGUAGE PackageImports #-} module Progress (tests) where +import Control.Concurrent.STM+import Data.Foldable (for_) import qualified Data.HashMap.Strict as Map+import Development.IDE (NormalizedFilePath) import Development.IDE.Core.ProgressReporting+import qualified "list-t" ListT+import qualified StmContainers.Map as STM import Test.Tasty import Test.Tasty.HUnit @@ -10,6 +16,11 @@ [ reportProgressTests ] +data InProgressModel = InProgressModel {+ done, todo :: Int,+ current :: Map.HashMap NormalizedFilePath Int+}+ reportProgressTests :: TestTree reportProgressTests = testGroup "recordProgress" [ test "addNew" addNew@@ -18,11 +29,32 @@ , test "done" done ] where- p0 = InProgress 0 0 mempty- addNew = recordProgress "A" succ p0- increase = recordProgress "A" succ addNew- decrease = recordProgress "A" succ increase- done = recordProgress "A" pred decrease- model InProgress{..} =+ p0 = pure $ InProgressModel 0 0 mempty+ addNew = recordProgressModel "A" succ p0+ increase = recordProgressModel "A" succ addNew+ decrease = recordProgressModel "A" succ increase+ done = recordProgressModel "A" pred decrease+ recordProgressModel key change state =+ model state $ \st -> recordProgress st key change+ model stateModelIO k = do+ state <- fromModel =<< stateModelIO+ k state+ toModel state+ test name p = testCase name $ do+ InProgressModel{..} <- p (done, todo) @?= (length (filter (==0) (Map.elems current)), Map.size current)- test name p = testCase name $ model p++fromModel :: InProgressModel -> IO InProgressState+fromModel InProgressModel{..} = do+ doneVar <- newTVarIO done+ todoVar <- newTVarIO todo+ currentVar <- STM.newIO+ atomically $ for_ (Map.toList current) $ \(k,v) -> STM.insert v k currentVar+ return InProgressState{..}++toModel :: InProgressState -> IO InProgressModel+toModel InProgressState{..} = atomically $ do+ done <- readTVar doneVar+ todo <- readTVar todoVar+ current <- Map.fromList <$> ListT.toList (STM.listT currentVar)+ return InProgressModel{..}
test/src/Development/IDE/Test.hs view
@@ -29,14 +29,16 @@ , getStoredKeys , waitForCustomMessage , waitForGC- ,getBuildKeysBuilt,getBuildKeysVisited,getBuildKeysChanged,getBuildEdgesCount) where+ ,getBuildKeysBuilt,getBuildKeysVisited,getBuildKeysChanged,getBuildEdgesCount,configureCheckProject) where import Control.Applicative.Combinators import Control.Lens hiding (List) import Control.Monad import Control.Monad.IO.Class+import Data.Aeson (toJSON) import qualified Data.Aeson as A import Data.Bifunctor (second)+import Data.Default import qualified Data.Map.Strict as Map import Data.Maybe (fromJust) import Data.Text (Text)@@ -45,7 +47,7 @@ WaitForIdeRuleResult, ideResultSuccess) import Development.IDE.Test.Diagnostic-import Ide.Plugin.Config (CheckParents)+import Ide.Plugin.Config (CheckParents, checkProject) import Language.LSP.Test hiding (message) import qualified Language.LSP.Test as LspTest import Language.LSP.Types hiding@@ -246,3 +248,9 @@ case A.fromJSON v of A.Success x -> Just x _ -> Nothing++configureCheckProject :: Bool -> Session ()+configureCheckProject overrideCheckProject =+ sendNotification SWorkspaceDidChangeConfiguration+ (DidChangeConfigurationParams $ toJSON+ def{checkProject = overrideCheckProject})
test/src/Development/IDE/Test/Diagnostic.hs view
@@ -7,7 +7,7 @@ import Language.LSP.Types.Lens as Lsp -- | (0-based line number, 0-based column number)-type Cursor = (Int, Int)+type Cursor = (UInt, UInt) cursorPosition :: Cursor -> Position cursorPosition (line, col) = Position line col