This is Mark's proposal (2026-06-07): "build a list of the methods in some graph, then just analyze references to those methods — would work well with procedure implementations as well."
class ReferenceIndex {
// symbolKey → reference sites across the whole solution
private bySymbol: Map<string, ReferenceSite[]>;
// file uri → the symbolKeys it contributes sites to (for incremental invalidation)
private byFile: Map<string, Set<string>>;
count(symbolKey): number; // O(1)
references(symbolKey): ReferenceSite[];
reindexFile(uri, sites): void; // remove old contributions of uri, add new
removeFile(uri): void;
isReady(): boolean;
}
ReferenceSite = { uri, line, character };
Goal
Make reference counts (and eventually Find-All-References / "who calls X") O(refs) lookups instead of per-request searches. After #188 the per-method CodeLens cost is dominated by the reference search scanning the file set on every count (~1–2s/method). A precomputed
symbol → [reference sites]index turns each count into a map lookup, independent of how many methods a file has.This is Mark's proposal (2026-06-07): "build a list of the methods in some graph, then just analyze references to those methods — would work well with procedure implementations as well."
Why it's feasible now
TokenCache.getTokensForClosedFile) — so a one-pass build over the solution is affordable, where before it would've taken minutes.FileRelationshipGraph(file edges),StructureDeclarationIndexer(declarations),DocumentStructure.procedureIndex(per-file decl index), and the resolution logic inReferencesProvider/MemberLocatorService/CallSiteArgumentClassifier.Data structure
symbolKey (normalized identity — must mirror how references resolve):
proc:<name>method:<class>.<method>method:<class>.<iface>.<method>(or fold to the iface method)type:<name>Start name-level (no overload-signature discrimination); add signature precision in a later phase (reuse [#120 follow-up] DefinitionProvider / MethodHoverResolver / ImplementationProvider still route through legacy selectBestOverload — type-aware findOverloadByArgClassifications not wired in #125/[#125 follow-up] Pattern A extension: arg-classification wire-up to SELF/PARENT/chained/cross-file paths in Def/Hover/Impl #127
CallSiteArgumentClassifier).Phasing (each independently shippable, behaviour-preserving)
ReferenceIndexclass + a pureextractReferenceSites(tokens, uri, resolve)that walks call/dot-access/type-usage sites and yields{symbolKey, site}. Unit-tested in isolation. Not wired to anything → zero behaviour risk.solutionReady(one pass over solution files, cheap tokens, yield per file per [perf][umbrella] Yield + honor cancellation across hot LSP search loops (WorkspaceSymbol, Implementation, overload/include walks) #187). PointonCodeLensResolveatindex.count(key)with fallback to the live FAR when the index isn't ready or misses (so it's never wrong, only faster). Measure.didChangeContent,reindexFile(changedUri)(usingbyFileto drop stale contributions) instead of the current globalcodeLensGenerationbump; refresh CodeLens.Correctness guardrails
index.count(sym) === provideReferences(sym).lengthon representative fixtures.byFileinvalidation so external edits don't serve stale counts (same discipline asgetTokensForClosedFile).Cross-ref
cooperativeCheckpointfor the build pass)ReferencesProvider,MemberLocatorService,CallSiteArgumentClassifier([#120 follow-up] DefinitionProvider / MethodHoverResolver / ImplementationProvider still route through legacy selectBestOverload — type-aware findOverloadByArgClassifications not wired in #125/[#125 follow-up] Pattern A extension: arg-classification wire-up to SELF/PARENT/chained/cross-file paths in Def/Hover/Impl #127/[#127 follow-up] Cross-file overload resolution must walk MEMBER → PROGRAM → INCLUDE chain (not just current file's INCLUDEs) #128)