-
-
Notifications
You must be signed in to change notification settings - Fork 399
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.