-
-
Notifications
You must be signed in to change notification settings - Fork 410
Description
I ran into this bug while debugging a flaky test case -
A.hs is initially not an FOI, so GetModIface A.hs has two dependencies IsFileOfInterest A.hs and GetModIfaceFromDiskAndIndex A.hs. Then A.hs is opened, turning it into an FOI
haskell-language-server/ghcide/src/Development/IDE/Core/Rules.hs
Lines 934 to 957 in dfd8f0b
| getModIfaceRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GetModIface f -> do | |
| fileOfInterest <- use_ IsFileOfInterest f | |
| res@(_,(_,mhmi)) <- case fileOfInterest of | |
| IsFOI status -> do | |
| -- Never load from disk for files of interest | |
| tmr <- use_ TypeCheck f | |
| linkableType <- getLinkableType f | |
| hsc <- hscEnv <$> use_ GhcSessionDeps f | |
| let compile = fmap ([],) $ use GenerateCore f | |
| se <- getShakeExtras | |
| (diags, !hiFile) <- writeCoreFileIfNeeded se hsc linkableType compile tmr | |
| let fp = hiFileFingerPrint <$> hiFile | |
| hiDiags <- case hiFile of | |
| Just hiFile | |
| | OnDisk <- status | |
| , not (tmrDeferredError tmr) -> liftIO $ writeHiFile se hsc hiFile | |
| _ -> pure [] | |
| return (fp, (diags++hiDiags, hiFile)) | |
| NotFOI -> do | |
| hiFile <- use GetModIfaceFromDiskAndIndex f | |
| let fp = hiFileFingerPrint <$> hiFile | |
| return (fp, ([], hiFile)) | |
| pure res |
At this point A.hs should have dependencies IsFileOfInterest A.hs, Typecheck A.hs ... (the IsFOI branch). However, I was seeing GetModIfaceFromDiskAndIndex still getting run for A.hs.
Investigating further, it turns out to be the implementation of refresh in hls-graph:
haskell-language-server/hls-graph/src/Development/IDE/Graph/Internal/Database.hs
Lines 145 to 148 in dfd8f0b
| refresh db stack key result = case (addStack key stack, result) of | |
| (Left e, _) -> throw e | |
| (Right stack, Just me@Result{resultDeps = ResultDeps (toListKeySet -> deps)}) -> do | |
| res <- builder db stack deps |
It seems like it speculatively builds all the previous dependencies of the key even if they might have changed. This is wrong as rules can have side effects or require preconditions (GetModIfaceFromDiskAndIndex has both). This also can lead to spurious diagnostics from these pseudo-dependencies.
We should instead build dependencies in the order that they occurred (which we currently do not maintain), and recompute with RunDependenciesChanged if any of them are dirty, short circuiting the evaluation of later dependencies (since they might not be true dependencies). However, this might have a negative impact on performance.
In the above case, that would mean skipping GetModIfaceFromDiskAndIndex after we discover that IsFOI has changed.