@@ -42,7 +42,6 @@ module Development.IDE.Core.Shake(
4242 RuleBody (.. ),
4343 define , defineNoDiagnostics ,
4444 defineEarlyCutoff ,
45- defineOnDisk , needOnDisk , needOnDisks ,
4645 defineNoFile , defineEarlyCutOffNoFile ,
4746 getDiagnostics ,
4847 mRunLspT , mRunLspTCallback ,
@@ -63,7 +62,6 @@ module Development.IDE.Core.Shake(
6362 Priority (.. ),
6463 updatePositionMapping ,
6564 deleteValue , recordDirtyKeys ,
66- OnDiskRule (.. ),
6765 WithProgressFunc , WithIndefiniteProgressFunc ,
6866 ProgressEvent (.. ),
6967 DelayedAction , mkDelayedAction ,
@@ -168,6 +166,7 @@ import qualified "list-t" ListT
168166import OpenTelemetry.Eventlog
169167import qualified StmContainers.Map as STM
170168import System.FilePath hiding (makeRelative )
169+ import System.IO.Unsafe (unsafePerformIO )
171170import System.Time.Extra
172171
173172data Log
@@ -1026,6 +1025,10 @@ usesWithStale key files = do
10261025 -- whether the rule succeeded or not.
10271026 mapM (lastValue key) files
10281027
1028+ useWithoutDependency :: IdeRule k v
1029+ => k -> NormalizedFilePath -> Action (Maybe v )
1030+ useWithoutDependency key file =
1031+ (\ [A value] -> currentValue value) <$> applyWithoutDependency [Q (key, file)]
10291032
10301033data RuleBody k v
10311034 = Rule (k -> NormalizedFilePath -> Action (Maybe BS. ByteString , IdeResult v ))
@@ -1044,28 +1047,28 @@ defineEarlyCutoff
10441047 -> Rules ()
10451048defineEarlyCutoff recorder (Rule op) = addRule $ \ (Q (key, file)) (old :: Maybe BS. ByteString ) mode -> otTracedAction key file mode traceA $ \ traceDiagnostics -> do
10461049 extras <- getShakeExtras
1047- let diagnostics diags = do
1050+ let diagnostics ver diags = do
10481051 traceDiagnostics diags
1049- updateFileDiagnostics recorder file (Key key) extras . map (\ (_,y,z) -> (y,z)) $ diags
1052+ updateFileDiagnostics recorder file ver (Key key) extras . map (\ (_,y,z) -> (y,z)) $ diags
10501053 defineEarlyCutoff' diagnostics (==) key file old mode $ const $ op key file
10511054defineEarlyCutoff recorder (RuleNoDiagnostics op) = addRule $ \ (Q (key, file)) (old :: Maybe BS. ByteString ) mode -> otTracedAction key file mode traceA $ \ traceDiagnostics -> do
1052- let diagnostics diags = do
1055+ let diagnostics _ver diags = do
10531056 traceDiagnostics diags
10541057 mapM_ (logWith recorder Warning . LogDefineEarlyCutoffRuleNoDiagHasDiag ) diags
10551058 defineEarlyCutoff' diagnostics (==) key file old mode $ const $ second (mempty ,) <$> op key file
10561059defineEarlyCutoff recorder RuleWithCustomNewnessCheck {.. } =
10571060 addRule $ \ (Q (key, file)) (old :: Maybe BS. ByteString ) mode ->
10581061 otTracedAction key file mode traceA $ \ traceDiagnostics -> do
1059- let diagnostics diags = do
1062+ let diagnostics _ver diags = do
10601063 traceDiagnostics diags
10611064 mapM_ (logWith recorder Warning . LogDefineEarlyCutoffRuleCustomNewnessHasDiag ) diags
10621065 defineEarlyCutoff' diagnostics newnessCheck key file old mode $
10631066 const $ second (mempty ,) <$> build key file
10641067defineEarlyCutoff recorder (RuleWithOldValue op) = addRule $ \ (Q (key, file)) (old :: Maybe BS. ByteString ) mode -> otTracedAction key file mode traceA $ \ traceDiagnostics -> do
10651068 extras <- getShakeExtras
1066- let diagnostics diags = do
1069+ let diagnostics ver diags = do
10671070 traceDiagnostics diags
1068- updateFileDiagnostics recorder file (Key key) extras . map (\ (_,y,z) -> (y,z)) $ diags
1071+ updateFileDiagnostics recorder file ver (Key key) extras . map (\ (_,y,z) -> (y,z)) $ diags
10691072 defineEarlyCutoff' diagnostics (==) key file old mode $ op key file
10701073
10711074defineNoFile :: IdeRule k v => Recorder (WithPriority Log ) -> (k -> Action v ) -> Rules ()
@@ -1080,7 +1083,7 @@ defineEarlyCutOffNoFile recorder f = defineEarlyCutoff recorder $ RuleNoDiagnost
10801083
10811084defineEarlyCutoff'
10821085 :: forall k v . IdeRule k v
1083- => ([FileDiagnostic ] -> Action () ) -- ^ update diagnostics
1086+ => (TextDocumentVersion -> [FileDiagnostic ] -> Action () ) -- ^ update diagnostics
10841087 -- | compare current and previous for freshness
10851088 -> (BS. ByteString -> BS. ByteString -> Bool )
10861089 -> k
@@ -1099,8 +1102,9 @@ defineEarlyCutoff' doDiagnostics cmp key file old mode action = do
10991102 case v of
11001103 -- No changes in the dependencies and we have
11011104 -- an existing successful result.
1102- Just (v@ Succeeded {}, diags) -> do
1103- doDiagnostics $ Vector. toList diags
1105+ Just (v@ (Succeeded _ x), diags) -> do
1106+ ver <- estimateFileVersionUnsafely state key (Just x) file
1107+ doDiagnostics (vfsVersion =<< ver) $ Vector. toList diags
11041108 return $ Just $ RunResult ChangedNothing old $ A v
11051109 _ -> return Nothing
11061110 _ ->
@@ -1120,18 +1124,13 @@ defineEarlyCutoff' doDiagnostics cmp key file old mode action = do
11201124 \ (e :: SomeException ) -> do
11211125 pure (Nothing , ([ideErrorText file $ T. pack $ show e | not $ isBadDependency e],Nothing ))
11221126
1123- modTime <- case eqT @ k @ GetModificationTime of
1124- Just Refl -> pure res
1125- Nothing
1126- | file == emptyFilePath -> pure Nothing
1127- | otherwise -> liftIO $ (currentValue . fst =<< ) <$> atomicallyNamed " define - read 2" (getValues state GetModificationTime file)
1128-
1127+ ver <- estimateFileVersionUnsafely state key res file
11291128 (bs, res) <- case res of
11301129 Nothing -> do
11311130 pure (toShakeValue ShakeStale bs, staleV)
1132- Just v -> pure (maybe ShakeNoCutoff ShakeResult bs, Succeeded modTime v)
1131+ Just v -> pure (maybe ShakeNoCutoff ShakeResult bs, Succeeded ver v)
11331132 liftIO $ atomicallyNamed " define - write" $ setValues state key file res (Vector. fromList diags)
1134- doDiagnostics diags
1133+ doDiagnostics (vfsVersion =<< ver) diags
11351134 let eq = case (bs, fmap decodeShakeValue old) of
11361135 (ShakeResult a, Just (ShakeResult b)) -> cmp a b
11371136 (ShakeStale a, Just (ShakeStale b)) -> cmp a b
@@ -1144,117 +1143,74 @@ defineEarlyCutoff' doDiagnostics cmp key file old mode action = do
11441143 A res
11451144 liftIO $ atomicallyNamed " define - dirtyKeys" $ modifyTVar' dirtyKeys (HSet. delete $ toKey key file)
11461145 return res
1146+ where
1147+ -- Highly unsafe helper to compute the version of a file
1148+ -- without creating a dependency on the GetModificationTime rule
1149+ -- (and without creating cycles in the build graph).
1150+ estimateFileVersionUnsafely
1151+ :: forall k v
1152+ . IdeRule k v
1153+ => Values
1154+ -> k
1155+ -> Maybe v
1156+ -> NormalizedFilePath
1157+ -> Action (Maybe FileVersion )
1158+ estimateFileVersionUnsafely state _k v fp
1159+ | fp == emptyFilePath = pure Nothing
1160+ | Just Refl <- eqT @ k @ GetModificationTime = pure v
1161+ -- GetModificationTime depends on these rules, so avoid creating a cycle
1162+ | Just Refl <- eqT @ k @ AddWatchedFile = pure Nothing
1163+ | Just Refl <- eqT @ k @ IsFileOfInterest = pure Nothing
1164+ -- GetFileExists gets called for missing files
1165+ | Just Refl <- eqT @ k @ GetFileExists = pure Nothing
1166+ -- For all other rules - compute the version properly without:
1167+ -- * creating a dependency: If everything depends on GetModificationTime, we lose early cutoff
1168+ -- * creating bogus "file does not exists" diagnostics
1169+ | otherwise = useWithoutDependency (GetModificationTime_ False ) fp
11471170
11481171traceA :: A v -> String
11491172traceA (A Failed {}) = " Failed"
11501173traceA (A Stale {}) = " Stale"
11511174traceA (A Succeeded {}) = " Success"
11521175
1153- -- | Rule type, input file
1154- data QDisk k = QDisk k NormalizedFilePath
1155- deriving (Eq , Generic )
1156-
1157- instance Hashable k => Hashable (QDisk k )
1158-
1159- instance NFData k => NFData (QDisk k )
1160-
1161- instance Show k => Show (QDisk k ) where
1162- show (QDisk k file) =
1163- show k ++ " ; " ++ fromNormalizedFilePath file
1164-
1165- type instance RuleResult (QDisk k ) = Bool
1166-
1167- data OnDiskRule = OnDiskRule
1168- { getHash :: Action BS. ByteString
1169- -- This is used to figure out if the state on disk corresponds to the state in the Shake
1170- -- database and we can therefore avoid rerunning. Often this can just be the file hash but
1171- -- in some cases we can be more aggressive, e.g., for GHC interface files this can be the ABI hash which
1172- -- is more stable than the hash of the interface file.
1173- -- An empty bytestring indicates that the state on disk is invalid, e.g., files are missing.
1174- -- We do not use a Maybe since we have to deal with encoding things into a ByteString anyway in the Shake DB.
1175- , runRule :: Action (IdeResult BS. ByteString )
1176- -- The actual rule code which produces the new hash (or Nothing if the rule failed) and the diagnostics.
1177- }
1178-
1179- -- This is used by the DAML compiler for incremental builds. Right now this is not used by
1180- -- ghcide itself but that might change in the future.
1181- -- The reason why this code lives in ghcide and in particular in this module is that it depends quite heavily on
1182- -- the internals of this module that we do not want to expose.
1183- defineOnDisk
1184- :: (Shake. ShakeValue k , RuleResult k ~ () )
1185- => Recorder (WithPriority Log )
1186- -> (k -> NormalizedFilePath -> OnDiskRule )
1187- -> Rules ()
1188- defineOnDisk recorder act = addRule $
1189- \ (QDisk key file) (mbOld :: Maybe BS. ByteString ) mode -> do
1190- extras <- getShakeExtras
1191- let OnDiskRule {.. } = act key file
1192- let validateHash h
1193- | BS. null h = Nothing
1194- | otherwise = Just h
1195- let runAct = actionCatch runRule $
1196- \ (e :: SomeException ) -> pure ([ideErrorText file $ T. pack $ displayException e | not $ isBadDependency e], Nothing )
1197- case mbOld of
1198- Nothing -> do
1199- (diags, mbHash) <- runAct
1200- updateFileDiagnostics recorder file (Key key) extras $ map (\ (_,y,z) -> (y,z)) diags
1201- pure $ RunResult ChangedRecomputeDiff (fromMaybe " " mbHash) (isJust mbHash)
1202- Just old -> do
1203- current <- validateHash <$> (actionCatch getHash $ \ (_ :: SomeException ) -> pure " " )
1204- if mode == RunDependenciesSame && Just old == current && not (BS. null old)
1205- then
1206- -- None of our dependencies changed, we’ve had a successful run before and
1207- -- the state on disk matches the state in the Shake database.
1208- pure $ RunResult ChangedNothing (fromMaybe " " current) (isJust current)
1209- else do
1210- (diags, mbHash) <- runAct
1211- updateFileDiagnostics recorder file (Key key) extras $ map (\ (_,y,z) -> (y,z)) diags
1212- let change
1213- | mbHash == Just old = ChangedRecomputeSame
1214- | otherwise = ChangedRecomputeDiff
1215- pure $ RunResult change (fromMaybe " " mbHash) (isJust mbHash)
1216-
1217- needOnDisk :: (Shake. ShakeValue k , RuleResult k ~ () ) => k -> NormalizedFilePath -> Action ()
1218- needOnDisk k file = do
1219- successfull <- apply1 (QDisk k file)
1220- liftIO $ unless successfull $ throwIO $ BadDependency (show k)
1221-
1222- needOnDisks :: (Shake. ShakeValue k , RuleResult k ~ () ) => k -> [NormalizedFilePath ] -> Action ()
1223- needOnDisks k files = do
1224- successfulls <- apply $ map (QDisk k) files
1225- liftIO $ unless (and successfulls) $ throwIO $ BadDependency (show k)
1226-
12271176updateFileDiagnostics :: MonadIO m
12281177 => Recorder (WithPriority Log )
12291178 -> NormalizedFilePath
1179+ -> TextDocumentVersion
12301180 -> Key
12311181 -> ShakeExtras
12321182 -> [(ShowDiagnostic ,Diagnostic )] -- ^ current results
12331183 -> m ()
1234- updateFileDiagnostics recorder fp k ShakeExtras {diagnostics, hiddenDiagnostics, publishedDiagnostics, state, debouncer, lspEnv} current = liftIO $ do
1235- modTime <- (currentValue . fst =<< ) <$> atomicallyNamed " diagnostics - read" (getValues state GetModificationTime fp)
1184+ updateFileDiagnostics recorder fp ver k ShakeExtras {diagnostics, hiddenDiagnostics, publishedDiagnostics, debouncer, lspEnv} current =
1185+ liftIO $ withTrace (" update diagnostics " <> fromString(fromNormalizedFilePath fp)) $ \ addTag -> do
1186+ addTag " key" (show k)
12361187 let (currentShown, currentHidden) = partition ((== ShowDiag ) . fst ) current
12371188 uri = filePathToUri' fp
1238- ver = vfsVersion =<< modTime
1239- update new store = setStageDiagnostics uri ver (T. pack $ show k) new store
1189+ addTagUnsafe :: String -> String -> String -> a -> a
1190+ addTagUnsafe msg t x v = unsafePerformIO(addTag (msg <> t) x) `seq` v
1191+ update :: (forall a . String -> String -> a -> a ) -> [Diagnostic ] -> STMDiagnosticStore -> STM [Diagnostic ]
1192+ update addTagUnsafe new store = addTagUnsafe " count" (show $ Prelude. length new) $ setStageDiagnostics addTagUnsafe uri ver (T. pack $ show k) new store
1193+ addTag " version" (show ver)
12401194 mask_ $ do
12411195 -- Mask async exceptions to ensure that updated diagnostics are always
12421196 -- published. Otherwise, we might never publish certain diagnostics if
12431197 -- an exception strikes between modifyVar but before
12441198 -- publishDiagnosticsNotification.
1245- newDiags <- liftIO $ atomicallyNamed " diagnostics - update" $ update (map snd currentShown) diagnostics
1246- _ <- liftIO $ atomicallyNamed " diagnostics - hidden" $ update (map snd currentHidden) hiddenDiagnostics
1199+ newDiags <- liftIO $ atomicallyNamed " diagnostics - update" $ update (addTagUnsafe " shown " ) ( map snd currentShown) diagnostics
1200+ _ <- liftIO $ atomicallyNamed " diagnostics - hidden" $ update (addTagUnsafe " hidden " ) ( map snd currentHidden) hiddenDiagnostics
12471201 let uri = filePathToUri' fp
12481202 let delay = if null newDiags then 0.1 else 0
1249- registerEvent debouncer delay uri $ do
1203+ registerEvent debouncer delay uri $ withTrace ( " report diagnostics " <> fromString (fromNormalizedFilePath fp)) $ \ tag -> do
12501204 join $ mask_ $ do
12511205 lastPublish <- atomicallyNamed " diagnostics - publish" $ STM. focus (Focus. lookupWithDefault [] <* Focus. insert newDiags) uri publishedDiagnostics
12521206 let action = when (lastPublish /= newDiags) $ case lspEnv of
12531207 Nothing -> -- Print an LSP event.
12541208 logWith recorder Info $ LogDiagsDiffButNoLspEnv (map (fp, ShowDiag ,) newDiags)
1255- Just env -> LSP. runLspT env $
1209+ Just env -> LSP. runLspT env $ do
1210+ liftIO $ tag " count" (show $ Prelude. length newDiags)
1211+ liftIO $ tag " key" (show k)
12561212 LSP. sendNotification LSP. STextDocumentPublishDiagnostics $
1257- LSP. PublishDiagnosticsParams (fromNormalizedUri uri) (fmap fromIntegral ver) (List newDiags)
1213+ LSP. PublishDiagnosticsParams (fromNormalizedUri uri) (fmap fromIntegral ver) (List newDiags)
12581214 return action
12591215
12601216newtype Priority = Priority Double
@@ -1276,26 +1232,33 @@ type STMDiagnosticStore = STM.Map NormalizedUri StoreItem
12761232getDiagnosticsFromStore :: StoreItem -> [Diagnostic ]
12771233getDiagnosticsFromStore (StoreItem _ diags) = concatMap SL. fromSortedList $ Map. elems diags
12781234
1279- updateSTMDiagnostics :: STMDiagnosticStore
1280- -> NormalizedUri -> TextDocumentVersion -> DiagnosticsBySource
1281- -> STM [LSP. Diagnostic ]
1282- updateSTMDiagnostics store uri mv newDiagsBySource =
1235+ updateSTMDiagnostics ::
1236+ (forall a . String -> String -> a -> a ) ->
1237+ STMDiagnosticStore ->
1238+ NormalizedUri ->
1239+ TextDocumentVersion ->
1240+ DiagnosticsBySource ->
1241+ STM [LSP. Diagnostic ]
1242+ updateSTMDiagnostics addTag store uri mv newDiagsBySource =
12831243 getDiagnosticsFromStore . fromJust <$> STM. focus (Focus. alter update *> Focus. lookup ) uri store
12841244 where
12851245 update (Just (StoreItem mvs dbs))
1246+ | addTag " previous version" (show mvs) $
1247+ addTag " previous count" (show $ Prelude. length $ filter (not . null ) $ Map. elems dbs) False = undefined
12861248 | mvs == mv = Just (StoreItem mv (newDiagsBySource <> dbs))
12871249 update _ = Just (StoreItem mv newDiagsBySource)
12881250
12891251-- | Sets the diagnostics for a file and compilation step
12901252-- if you want to clear the diagnostics call this with an empty list
12911253setStageDiagnostics
1292- :: NormalizedUri
1254+ :: (forall a . String -> String -> a -> a )
1255+ -> NormalizedUri
12931256 -> TextDocumentVersion -- ^ the time that the file these diagnostics originate from was last edited
12941257 -> T. Text
12951258 -> [LSP. Diagnostic ]
12961259 -> STMDiagnosticStore
12971260 -> STM [LSP. Diagnostic ]
1298- setStageDiagnostics uri ver stage diags ds = updateSTMDiagnostics ds uri ver updatedDiags
1261+ setStageDiagnostics addTag uri ver stage diags ds = updateSTMDiagnostics addTag ds uri ver updatedDiags
12991262 where
13001263 ! updatedDiags = Map. singleton (Just stage) $! SL. toSortedList diags
13011264
0 commit comments