Spun off from review of #84 (comment on ServerImpl.scala:342): "Shouldnt we update all opened files?"
Context
ServerImpl.pcDiagnostics runs on every textDocument/didChange for the file the user is editing. After computing diagnostics it produces a SemanticDB for that file only and feeds it into IndexManager.updateOpenFile(uri, syms, refs):
_ <- IO
.interruptible(pc.semanticdbTextDocument(uri.toURI, textDocument.content))
.flatMap { bytes =>
val (syms, refs) = index.SemanticdbIndexer.indexDocument(uri, bytes, info.displayName)
indexManager.updateOpenFile(uri, syms, refs)
}
Other files the user has open in the editor are not re-indexed.
Why this might be wrong
ProjectIndex.references keys ref entries by target SymbolId but stores per-source-file Locations. If file A.scala references a symbol defined in file B.scala, then:
- The user edits
B.scala → only B.scala's symbol/ref entries get refreshed.
A.scala's reference targets still resolve to the (new) SymbolId for B, so find-references probably still works.
- But
A.scala's reference locations in refsByFile are based on whatever PC content was indexed last — usually the on-disk version from the most recent compile. They go stale only when the locations inside A.scala shift, which can happen if the user edits A.scala, but that's already handled.
So the obvious failure mode (ref locations in A drift because B changed) doesn't actually exist — refs in A point into A, not into B. The location of the target (the def in B) lives in IndexedSymbol keyed by the B URI and gets refreshed when B's updateOpenFile runs.
What might still be wrong
- Type-driven ref resolution. If editing
B.scala changes a type, the references in A.scala might now point to a different overload / parent / generic instantiation. The SemanticDB symbol id in A's cached refs doesn't update until A itself is re-typed.
- Cross-file rename / move. If
B introduces a new public symbol that shadows one in A's import scope, A's refs are stale until A is touched.
- Compile-driven update.
onCompilationComplete does re-index changed files (driven by Zinc's changedFiles), so on save the situation self-corrects for any file Zinc decided was affected. The window of staleness is "between edit and next save".
Question to resolve
Is the on-save (onCompilationComplete) refresh enough, or do we need to re-run semanticdbTextDocument for every open file on every keystroke? The latter is expensive (N PC roundtrips per keystroke) and would need debouncing.
Possible directions:
- Status quo — accept that edit-time refs in non-active files can be stale for a few seconds until save; rely on
onCompilationComplete to recover.
- Re-index all open files on debounced timer — periodically (e.g., every 2s of idle) call
semanticdbTextDocument for each open URI.
- Re-index dependents only — when
B changes, walk ProjectIndex.references to find which files reference symbols defined in B, and re-index only those.
(3) is the right answer in theory but requires a "file depends on" inverse index we don't currently maintain.
Scope
Out of scope for Phase 4 (visibility fix); revisit alongside Phase 5 (lifecycle / readiness) or when the LSP feature handlers (5.x — references, workspace/symbol) land and we have concrete user-visible staleness bugs.
Spun off from review of #84 (comment on ServerImpl.scala:342): "Shouldnt we update all opened files?"
Context
ServerImpl.pcDiagnosticsruns on everytextDocument/didChangefor the file the user is editing. After computing diagnostics it produces a SemanticDB for that file only and feeds it intoIndexManager.updateOpenFile(uri, syms, refs):Other files the user has open in the editor are not re-indexed.
Why this might be wrong
ProjectIndex.referenceskeys ref entries by targetSymbolIdbut stores per-source-fileLocations. If fileA.scalareferences a symbol defined in fileB.scala, then:B.scala→ onlyB.scala's symbol/ref entries get refreshed.A.scala's reference targets still resolve to the (new)SymbolIdforB, so find-references probably still works.A.scala's reference locations inrefsByFileare based on whatever PC content was indexed last — usually the on-disk version from the most recent compile. They go stale only when the locations insideA.scalashift, which can happen if the user editsA.scala, but that's already handled.So the obvious failure mode (ref locations in
Adrift becauseBchanged) doesn't actually exist — refs inApoint intoA, not intoB. The location of the target (the def inB) lives inIndexedSymbolkeyed by theBURI and gets refreshed whenB'supdateOpenFileruns.What might still be wrong
B.scalachanges a type, the references inA.scalamight now point to a different overload / parent / generic instantiation. The SemanticDB symbol id inA's cached refs doesn't update untilAitself is re-typed.Bintroduces a new public symbol that shadows one inA's import scope,A's refs are stale untilAis touched.onCompilationCompletedoes re-index changed files (driven by Zinc'schangedFiles), so on save the situation self-corrects for any file Zinc decided was affected. The window of staleness is "between edit and next save".Question to resolve
Is the on-save (
onCompilationComplete) refresh enough, or do we need to re-runsemanticdbTextDocumentfor every open file on every keystroke? The latter is expensive (N PC roundtrips per keystroke) and would need debouncing.Possible directions:
onCompilationCompleteto recover.semanticdbTextDocumentfor each open URI.Bchanges, walkProjectIndex.referencesto find which files reference symbols defined inB, and re-index only those.(3) is the right answer in theory but requires a "file depends on" inverse index we don't currently maintain.
Scope
Out of scope for Phase 4 (visibility fix); revisit alongside Phase 5 (lifecycle / readiness) or when the LSP feature handlers (5.x — references, workspace/symbol) land and we have concrete user-visible staleness bugs.