Write ifaces on save (#760)

* Write ifaces on save

* Move isFileOfInterestRule to FileStore.hs and use real mtime for saved files

* hlint

* Add test

* fix flaky tests

* Only check for hie file in >= 8.6
This commit is contained in:
wz1000 2020-09-12 01:28:23 +05:30 committed by GitHub
parent 1ed280be46
commit 15ab2ff3ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 144 additions and 103 deletions

View File

@ -46,7 +46,7 @@ import System.FilePath
import System.Time.Extra
import Paths_ghcide
import Development.GitRev
import qualified Data.HashSet as HashSet
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Aeson as J
import HIE.Bios.Cradle
@ -144,7 +144,7 @@ main = do
ide <- initialise def mainRule (pure $ IdInt 0) (showEvent lock) dummyWithProg (const (const id)) (logger logLevel) debouncer (defaultIdeOptions sessionLoader) vfs
putStrLn "\nStep 4/4: Type checking the files"
setFilesOfInterest ide $ HashSet.fromList $ map toNormalizedFilePath' files
setFilesOfInterest ide $ HashMap.fromList $ map ((, OnDisk) . toNormalizedFilePath') files
results <- runAction "User TypeCheck" ide $ uses TypeCheck (map toNormalizedFilePath' files)
let (worked, failed) = partition fst $ zip (map isJust results) files
when (failed /= []) $

View File

@ -14,13 +14,15 @@ module Development.IDE.Core.FileStore(
typecheckParents,
VFSHandle,
makeVFSHandle,
makeLSPVFSHandle
makeLSPVFSHandle,
isFileOfInterestRule
) where
import Development.IDE.GHC.Orphans()
import Development.IDE.Core.Shake
import Control.Concurrent.Extra
import qualified Data.Map.Strict as Map
import qualified Data.HashMap.Strict as HM
import Data.Maybe
import qualified Data.Text as T
import Control.Monad.Extra
@ -35,8 +37,9 @@ import System.IO.Error
import qualified Data.ByteString.Char8 as BS
import Development.IDE.Types.Diagnostics
import Development.IDE.Types.Location
import Development.IDE.Core.OfInterest (kick)
import Development.IDE.Core.OfInterest (getFilesOfInterest, kick)
import Development.IDE.Core.RuleTypes
import Development.IDE.Types.Options
import qualified Data.Rope.UTF16 as Rope
import Development.IDE.Import.DependencyInformation
@ -92,6 +95,12 @@ makeLSPVFSHandle lspFuncs = VFSHandle
}
isFileOfInterestRule :: Rules ()
isFileOfInterestRule = defineEarlyCutoff $ \IsFileOfInterest f -> do
filesOfInterest <- getFilesOfInterest
let res = maybe NotFOI IsFOI $ f `HM.lookup` filesOfInterest
return (Just $ BS.pack $ show $ hash res, ([], Just res))
-- | Get the contents of a file, either dirty (if the buffer is modified) or Nothing to mean use from disk.
type instance RuleResult GetFileContents = (FileVersion, Maybe T.Text)
@ -119,31 +128,31 @@ getModificationTimeRule vfs =
if isDoesNotExistError e && not missingFileDiags
then return (Nothing, ([], Nothing))
else return (Nothing, ([diag], Nothing))
where
-- Dir.getModificationTime is surprisingly slow since it performs
-- a ton of conversions. Since we do not actually care about
-- the format of the time, we can get away with something cheaper.
-- For now, we only try to do this on Unix systems where it seems to get the
-- time spent checking file modifications (which happens on every change)
-- from > 0.5s to ~0.15s.
-- We might also want to try speeding this up on Windows at some point.
-- TODO leverage DidChangeWatchedFile lsp notifications on clients that
-- support them, as done for GetFileExists
getModTime :: FilePath -> IO (Int64, Int64)
getModTime f =
-- Dir.getModificationTime is surprisingly slow since it performs
-- a ton of conversions. Since we do not actually care about
-- the format of the time, we can get away with something cheaper.
-- For now, we only try to do this on Unix systems where it seems to get the
-- time spent checking file modifications (which happens on every change)
-- from > 0.5s to ~0.15s.
-- We might also want to try speeding this up on Windows at some point.
-- TODO leverage DidChangeWatchedFile lsp notifications on clients that
-- support them, as done for GetFileExists
getModTime :: FilePath -> IO (Int64, Int64)
getModTime f =
#ifdef mingw32_HOST_OS
do time <- Dir.getModificationTime f
let !day = fromInteger $ toModifiedJulianDay $ utctDay time
!dayTime = fromInteger $ diffTimeToPicoseconds $ utctDayTime time
pure (day, dayTime)
do time <- Dir.getModificationTime f
let !day = fromInteger $ toModifiedJulianDay $ utctDay time
!dayTime = fromInteger $ diffTimeToPicoseconds $ utctDayTime time
pure (day, dayTime)
#else
withCString f $ \f' ->
alloca $ \secPtr ->
alloca $ \nsecPtr -> do
Posix.throwErrnoPathIfMinus1Retry_ "getmodtime" f $ c_getModTime f' secPtr nsecPtr
CTime sec <- peek secPtr
CLong nsec <- peek nsecPtr
pure (sec, nsec)
withCString f $ \f' ->
alloca $ \secPtr ->
alloca $ \nsecPtr -> do
Posix.throwErrnoPathIfMinus1Retry_ "getmodtime" f $ c_getModTime f' secPtr nsecPtr
CTime sec <- peek secPtr
CLong nsec <- peek nsecPtr
pure (sec, nsec)
-- Sadly even unixs getFileStatus + modificationTimeHiRes is still about twice as slow
-- as doing the FFI call ourselves :(.
@ -152,11 +161,14 @@ foreign import ccall "getmodtime" c_getModTime :: CString -> Ptr CTime -> Ptr CL
modificationTime :: FileVersion -> Maybe UTCTime
modificationTime VFSVersion{} = Nothing
modificationTime (ModificationTime large small) =
modificationTime (ModificationTime large small) = Just $ internalTimeToUTCTime large small
internalTimeToUTCTime :: Int64 -> Int64 -> UTCTime
internalTimeToUTCTime large small =
#ifdef mingw32_HOST_OS
Just (UTCTime (ModifiedJulianDay $ fromIntegral large) (picosecondsToDiffTime $ fromIntegral small))
UTCTime (ModifiedJulianDay $ fromIntegral large) (picosecondsToDiffTime $ fromIntegral small)
#else
Just (systemToUTCTime $ MkSystemTime large (fromIntegral small))
systemToUTCTime $ MkSystemTime large (fromIntegral small)
#endif
getFileContentsRule :: VFSHandle -> Rules ()
@ -182,7 +194,15 @@ ideTryIOException fp act =
getFileContents :: NormalizedFilePath -> Action (UTCTime, Maybe T.Text)
getFileContents f = do
(fv, txt) <- use_ GetFileContents f
modTime <- maybe (liftIO getCurrentTime) return $ modificationTime fv
modTime <- case modificationTime fv of
Just t -> pure t
Nothing -> do
foi <- use_ IsFileOfInterest f
liftIO $ case foi of
IsFOI Modified -> getCurrentTime
_ -> do
(large,small) <- getModTime $ fromNormalizedFilePath f
pure $ internalTimeToUTCTime large small
return (modTime, txt)
fileStoreRules :: VFSHandle -> Rules ()
@ -190,7 +210,7 @@ fileStoreRules vfs = do
addIdeGlobal vfs
getModificationTimeRule vfs
getFileContentsRule vfs
isFileOfInterestRule
-- | Notify the compiler service that a particular file has been modified.
-- Use 'Nothing' to say the file is no longer in the virtual file system
@ -205,13 +225,15 @@ setBufferModified state absFile contents = do
-- | Note that some buffer for a specific file has been modified but not
-- with what changes.
setFileModified :: IdeState
-> Bool -- ^ True indicates that we should also attempt to recompile
-- modules which depended on this file. Currently
-- it is true when saving but not on normal
-- document modification events
-> Bool -- ^ Was the file saved?
-> NormalizedFilePath
-> IO ()
setFileModified state prop nfp = do
setFileModified state saved nfp = do
ideOptions <- getIdeOptionsIO $ shakeExtras state
let checkParents = case optCheckParents ideOptions of
AlwaysCheck -> True
CheckOnSaveAndClose -> saved
_ -> False
VFSHandle{..} <- getIdeGlobalState state
when (isJust setVirtualFileContents) $
fail "setSomethingModified can't be called on this type of VFSHandle"
@ -221,7 +243,7 @@ setFileModified state prop nfp = do
void $ use GetSpanInfo nfp
liftIO $ progressUpdate KickCompleted
shakeRestart state [da]
when prop $
when checkParents $
typecheckParents state nfp
typecheckParents :: IdeState -> NormalizedFilePath -> IO ()

View File

@ -9,7 +9,7 @@
module Development.IDE.Core.OfInterest(
ofInterestRules,
getFilesOfInterest, setFilesOfInterest, modifyFilesOfInterest,
kick
kick, FileOfInterestStatus(..)
) where
import Control.Concurrent.Extra
@ -20,8 +20,8 @@ import GHC.Generics
import Data.Typeable
import qualified Data.ByteString.UTF8 as BS
import Control.Exception
import Data.HashSet (HashSet)
import qualified Data.HashSet as HashSet
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text as T
import Data.Tuple.Extra
import Development.Shake
@ -34,10 +34,10 @@ import Development.IDE.Core.Shake
import Data.Maybe (mapMaybe)
import GhcPlugins (HomeModInfo(hm_iface))
newtype OfInterestVar = OfInterestVar (Var (HashSet NormalizedFilePath))
newtype OfInterestVar = OfInterestVar (Var (HashMap NormalizedFilePath FileOfInterestStatus))
instance IsIdeGlobal OfInterestVar
type instance RuleResult GetFilesOfInterest = HashSet NormalizedFilePath
type instance RuleResult GetFilesOfInterest = HashMap NormalizedFilePath FileOfInterestStatus
data GetFilesOfInterest = GetFilesOfInterest
deriving (Eq, Show, Typeable, Generic)
@ -49,7 +49,7 @@ instance Binary GetFilesOfInterest
-- | The rule that initialises the files of interest state.
ofInterestRules :: Rules ()
ofInterestRules = do
addIdeGlobal . OfInterestVar =<< liftIO (newVar HashSet.empty)
addIdeGlobal . OfInterestVar =<< liftIO (newVar HashMap.empty)
defineEarlyCutoff $ \GetFilesOfInterest _file -> assert (null $ fromNormalizedFilePath _file) $ do
alwaysRerun
filesOfInterest <- getFilesOfInterestUntracked
@ -57,7 +57,7 @@ ofInterestRules = do
-- | Get the files that are open in the IDE.
getFilesOfInterest :: Action (HashSet NormalizedFilePath)
getFilesOfInterest :: Action (HashMap NormalizedFilePath FileOfInterestStatus)
getFilesOfInterest = useNoFile_ GetFilesOfInterest
@ -67,10 +67,10 @@ getFilesOfInterest = useNoFile_ GetFilesOfInterest
-- | Set the files-of-interest - not usually necessary or advisable.
-- The LSP client will keep this information up to date.
setFilesOfInterest :: IdeState -> HashSet NormalizedFilePath -> IO ()
setFilesOfInterest :: IdeState -> HashMap NormalizedFilePath FileOfInterestStatus -> IO ()
setFilesOfInterest state files = modifyFilesOfInterest state (const files)
getFilesOfInterestUntracked :: Action (HashSet NormalizedFilePath)
getFilesOfInterestUntracked :: Action (HashMap NormalizedFilePath FileOfInterestStatus)
getFilesOfInterestUntracked = do
OfInterestVar var <- getIdeGlobalAction
liftIO $ readVar var
@ -78,13 +78,13 @@ getFilesOfInterestUntracked = do
-- | Modify the files-of-interest - not usually necessary or advisable.
-- The LSP client will keep this information up to date.
modifyFilesOfInterest
:: IdeState
-> (HashSet NormalizedFilePath -> HashSet NormalizedFilePath)
-> IO ()
:: IdeState
-> (HashMap NormalizedFilePath FileOfInterestStatus -> HashMap NormalizedFilePath FileOfInterestStatus)
-> IO ()
modifyFilesOfInterest state f = do
OfInterestVar var <- getIdeGlobalState state
files <- modifyVar var $ pure . dupe . f
logDebug (ideLogger state) $ "Set files of interest to: " <> T.pack (show $ HashSet.toList files)
logDebug (ideLogger state) $ "Set files of interest to: " <> T.pack (show $ HashMap.toList files)
-- | Typecheck all the files of interest.
-- Could be improved
@ -95,7 +95,7 @@ kick = mkDelayedAction "kick" Debug $ do
liftIO $ progressUpdate KickStarted
-- Update the exports map for the project
results <- uses TypeCheck $ HashSet.toList files
results <- uses TypeCheck $ HashMap.keys files
ShakeExtras{exportsMap} <- getShakeExtras
let modIfaces = mapMaybe (fmap (hm_iface . tmrModInfo)) results
!exportsMap' = createExportsMap modIfaces

View File

@ -132,7 +132,19 @@ type instance RuleResult GetModIfaceFromDisk = HiFileResult
-- | Get a module interface details, either from an interface file or a typechecked module
type instance RuleResult GetModIface = HiFileResult
type instance RuleResult IsFileOfInterest = Bool
data FileOfInterestStatus = OnDisk | Modified
deriving (Eq, Show, Typeable, Generic)
instance Hashable FileOfInterestStatus
instance NFData FileOfInterestStatus
instance Binary FileOfInterestStatus
data IsFileOfInterestResult = NotFOI | IsFOI FileOfInterestStatus
deriving (Eq, Show, Typeable, Generic)
instance Hashable IsFileOfInterestResult
instance NFData IsFileOfInterestResult
instance Binary IsFileOfInterestResult
type instance RuleResult IsFileOfInterest = IsFileOfInterestResult
-- | Generate a ModSummary that has enough information to be used to get .hi and .hie files.
-- without needing to parse the entire source

View File

@ -190,7 +190,7 @@ getHomeHieFile f = do
hsc <- hscEnv <$> use_ GhcSession f
pm <- use_ GetParsedModule f
source <- getSourceFileSource f
typeCheckRuleDefinition hsc pm DoGenerateInterfaceFiles (Just source)
typeCheckRuleDefinition hsc pm NotFOI (Just source)
_ <- MaybeT $ liftIO $ timeout 1 wait
ncu <- mkUpdater
liftIO $ loadHieFile ncu hie_f
@ -527,7 +527,9 @@ typeCheckRule = define $ \TypeCheck file -> do
hsc <- hscEnv <$> use_ GhcSessionDeps file
-- do not generate interface files as this rule is called
-- for files of interest on every keystroke
typeCheckRuleDefinition hsc pm SkipGenerationOfInterfaceFiles Nothing
source <- getSourceFileSource file
isFoi <- use_ IsFileOfInterest file
typeCheckRuleDefinition hsc pm isFoi (Just source)
knownFilesRule :: Rules ()
knownFilesRule = defineEarlyCutOffNoFile $ \GetKnownFiles -> do
@ -541,11 +543,6 @@ getModuleGraphRule = defineNoFile $ \GetModuleGraph -> do
rawDepInfo <- rawDependencyInformation (HashSet.toList fs)
pure $ processDependencyInformation rawDepInfo
data GenerateInterfaceFiles
= DoGenerateInterfaceFiles
| SkipGenerationOfInterfaceFiles
deriving (Show)
-- This is factored out so it can be directly called from the GetModIface
-- rule. Directly calling this rule means that on the initial load we can
-- garbage collect all the intermediate typechecked modules rather than
@ -553,25 +550,28 @@ data GenerateInterfaceFiles
typeCheckRuleDefinition
:: HscEnv
-> ParsedModule
-> GenerateInterfaceFiles -- ^ Should generate .hi and .hie files ?
-> IsFileOfInterestResult -- ^ Should generate .hi and .hie files ?
-> Maybe BS.ByteString
-> Action (IdeResult TcModuleResult)
typeCheckRuleDefinition hsc pm generateArtifacts source = do
typeCheckRuleDefinition hsc pm isFoi source = do
setPriority priorityTypeCheck
IdeOptions { optDefer = defer } <- getIdeOptions
addUsageDependencies $ liftIO $ do
res <- typecheckModule defer hsc pm
case res of
(diags, Just (hsc,tcm))
| DoGenerateInterfaceFiles <- generateArtifacts
-- Don't save interface files for modules that compiled due to defering
-- type errors, as we won't get proper diagnostics if we load these from
-- disk
, not $ tmrDeferedError tcm -> do
diagsHie <- generateAndWriteHieFile hsc (tmrModule tcm) (fromMaybe "" source)
diagsHi <- writeHiFile hsc tcm
return (diags <> diagsHi <> diagsHie, Just tcm)
(diags, Just (hsc,tcm)) -> do
case isFoi of
IsFOI Modified -> return (diags, Just tcm)
_ -> do -- If the file is saved on disk, or is not a FOI, we write out ifaces
diagsHie <- generateAndWriteHieFile hsc (tmrModule tcm) (fromMaybe "" source)
-- Don't save interface files for modules that compiled due to defering
-- type errors, as we won't get proper diagnostics if we load these from
-- disk
diagsHi <- if not $ tmrDeferedError tcm
then writeHiFile hsc tcm
else pure mempty
return (diags <> diagsHi <> diagsHie, Just tcm)
(diags, res) ->
return (diags, snd <$> res)
where
@ -771,18 +771,18 @@ getModSummaryRule = do
getModIfaceRule :: Rules ()
getModIfaceRule = defineEarlyCutoff $ \GetModIface f -> do
#if MIN_GHC_API_VERSION(8,6,0) && !defined(GHC_LIB)
fileOfInterest <- use_ IsFileOfInterest f
if fileOfInterest
then do
-- Never load from disk for files of interest
tmr <- use TypeCheck f
let !hiFile = extractHiFileResult tmr
let fp = hiFileFingerPrint <$> hiFile
return (fp, ([], hiFile))
else do
hiFile <- use GetModIfaceFromDisk f
let fp = hiFileFingerPrint <$> hiFile
return (fp, ([], hiFile))
fileOfInterest <- use_ IsFileOfInterest f
case fileOfInterest of
IsFOI _ -> do
-- Never load from disk for files of interest
tmr <- use TypeCheck f
let !hiFile = extractHiFileResult tmr
let fp = hiFileFingerPrint <$> hiFile
return (fp, ([], hiFile))
NotFOI -> do
hiFile <- use GetModIfaceFromDisk f
let fp = hiFileFingerPrint <$> hiFile
return (fp, ([], hiFile))
#else
tm <- use TypeCheck f
let !hiFile = extractHiFileResult tm
@ -813,7 +813,7 @@ regenerateHiFile sess f = do
source <- getSourceFileSource f
-- Invoke typechecking directly to update it without incurring a dependency
-- on the parsed module and the typecheck rules
(diags', tmr) <- typeCheckRuleDefinition hsc pm DoGenerateInterfaceFiles (Just source)
(diags', tmr) <- typeCheckRuleDefinition hsc pm NotFOI (Just source)
-- Bang pattern is important to avoid leaking 'tmr'
let !res = extractHiFileResult tmr
return (diags <> diags', res)
@ -824,12 +824,6 @@ extractHiFileResult (Just tmr) =
-- Bang patterns are important to force the inner fields
Just $! tmr_hiFileResult tmr
isFileOfInterestRule :: Rules ()
isFileOfInterestRule = defineEarlyCutoff $ \IsFileOfInterest f -> do
filesOfInterest <- getFilesOfInterest
let res = f `elem` filesOfInterest
return (Just (if res then "1" else ""), ([], Just res))
-- | A rule that wires per-file rules together
mainRule :: Rules ()
mainRule = do
@ -845,7 +839,6 @@ mainRule = do
loadGhcSession
getModIfaceFromDiskRule
getModIfaceRule
isFileOfInterestRule
getModSummaryRule
isHiFileStableRule
getModuleGraphRule

View File

@ -23,6 +23,7 @@ import Development.IDE.Types.Options
import Control.Monad.Extra
import Data.Foldable as F
import Data.Maybe
import qualified Data.HashMap.Strict as M
import qualified Data.HashSet as S
import qualified Data.Text as Text
@ -39,32 +40,30 @@ setHandlersNotifications = PartialHandlers $ \WithMessage{..} x -> return x
{LSP.didOpenTextDocumentNotificationHandler = withNotification (LSP.didOpenTextDocumentNotificationHandler x) $
\_ ide (DidOpenTextDocumentParams TextDocumentItem{_uri,_version}) -> do
updatePositionMapping ide (VersionedTextDocumentIdentifier _uri (Just _version)) (List [])
IdeOptions{optCheckParents} <- getIdeOptionsIO $ shakeExtras ide
whenUriFile _uri $ \file -> do
modifyFilesOfInterest ide (S.insert file)
let checkParents = optCheckParents == AlwaysCheck
setFileModified ide checkParents file
modifyFilesOfInterest ide (M.insert file OnDisk)
setFileModified ide False file
logInfo (ideLogger ide) $ "Opened text document: " <> getUri _uri
,LSP.didChangeTextDocumentNotificationHandler = withNotification (LSP.didChangeTextDocumentNotificationHandler x) $
\_ ide (DidChangeTextDocumentParams identifier@VersionedTextDocumentIdentifier{_uri} changes) -> do
updatePositionMapping ide identifier changes
IdeOptions{optCheckParents} <- getIdeOptionsIO $ shakeExtras ide
let checkParents = optCheckParents == AlwaysCheck
whenUriFile _uri $ \file -> setFileModified ide checkParents file
whenUriFile _uri $ \file -> do
modifyFilesOfInterest ide (M.insert file Modified)
setFileModified ide False file
logInfo (ideLogger ide) $ "Modified text document: " <> getUri _uri
,LSP.didSaveTextDocumentNotificationHandler = withNotification (LSP.didSaveTextDocumentNotificationHandler x) $
\_ ide (DidSaveTextDocumentParams TextDocumentIdentifier{_uri}) -> do
IdeOptions{optCheckParents} <- getIdeOptionsIO $ shakeExtras ide
let checkParents = optCheckParents >= CheckOnSaveAndClose
whenUriFile _uri $ \file -> setFileModified ide checkParents file
whenUriFile _uri $ \file -> do
modifyFilesOfInterest ide (M.insert file OnDisk)
setFileModified ide True file
logInfo (ideLogger ide) $ "Saved text document: " <> getUri _uri
,LSP.didCloseTextDocumentNotificationHandler = withNotification (LSP.didCloseTextDocumentNotificationHandler x) $
\_ ide (DidCloseTextDocumentParams TextDocumentIdentifier{_uri}) -> do
whenUriFile _uri $ \file -> do
modifyFilesOfInterest ide (S.delete file)
modifyFilesOfInterest ide (M.delete file)
-- Refresh all the files that depended on this
IdeOptions{optCheckParents} <- getIdeOptionsIO $ shakeExtras ide
when (optCheckParents >= CheckOnClose) $ typecheckParents ide file

View File

@ -56,7 +56,7 @@ import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import System.Time.Extra
import Development.IDE.Plugin.CodeAction (typeSignatureCommandId, blockCommandId)
import Development.IDE.Plugin.Test (TestRequest(BlockSeconds))
import Development.IDE.Plugin.Test (TestRequest(BlockSeconds,GetInterfaceFilesDir))
main :: IO ()
main = do
@ -2992,6 +2992,21 @@ ifaceErrorTest = testCase "iface-error-test-1" $ withoutStackEnv $ runWithExtraF
-- save so that we can that the error propogates to A
sendNotification TextDocumentDidSave (DidSaveTextDocumentParams bdoc)
-- Check that we wrote the interfaces for B when we saved
lid <- sendRequest (CustomClientMethod "hidir") $ GetInterfaceFilesDir bPath
res <- skipManyTill (message :: Session WorkDoneProgressCreateRequest) $
skipManyTill (message :: Session WorkDoneProgressBeginNotification) $
responseForId lid
liftIO $ case res of
ResponseMessage{_result=Right hidir} -> do
hi_exists <- doesFileExist $ hidir </> "B.hi"
assertBool ("Couldn't find B.hi in " ++ hidir) hi_exists
#if MIN_GHC_API_VERSION(8,6,0)
hie_exists <- doesFileExist $ hidir </> "B.hie"
assertBool ("Couldn't find B.hie in " ++ hidir) hie_exists
#endif
_ -> assertFailure $ "Got malformed response for CustomMessage hidir: " ++ show res
-- Check that the error propogates to A
expectDiagnostics
[("A.hs", [(DsError, (5, 4), "Couldn't match expected type 'Int' with actual type 'Bool'")])]