References
What this page covers
This page documents how textDocument/references works in the current implementation:
- how the target symbol is resolved from the cursor,
- how same-file and cross-file references are collected,
- how Yul
externalReferencesare included, - what is covered by tests today, and what still needs explicit tests.
If you are looking for import-string navigation (for example import "./Pool.sol"), that path belongs to go-to-definition and is documented in goto.md.
The working model
references is built around one core data structure: CachedBuild.
CachedBuild is a snapshot built from successful compiler output on disk. Internally it stores HashMaps such as:
nodes: HashMap<abs_path, HashMap<node_id, NodeInfo>>id_to_path_map: HashMap<source_id, path>external_refs: HashMap<src_string, declaration_id>
NodeInfo includes fields like src, name_location, and referenced_declaration.
At request time, the server uses this snapshot to resolve references quickly, then merges results from other cached builds to get cross-file coverage.
Request flow in practice
In src/lsp.rs, the references handler does the following:
- Load source bytes and get (or build)
CachedBuildfor the URI. - Collect current-build references.
- Derive stable target location
(def_abs_path, def_byte_offset). - Scan other cached builds for cross-file references to the same target.
- Deduplicate by
(uri, start, end)and return.
This is why you get both local and cross-file references in one response when caches are available.
How target resolution works
Inside references.rs, resolution follows this order:
- Try Yul resolution first (
externalReferencesmapping). - Fall back to AST span match (smallest containing node).
- Normalize to declaration target: follow
referencedDeclarationwhen present, else use the node id directly.
That resolved node ID becomes the target for reference collection.
Yul references are first-class
Yul identifiers inside assembly {} do not have normal Solidity node IDs in the same way usage sites do. The implementation bridges this via InlineAssembly.externalReferences, which maps Yul src ranges to Solidity declaration IDs.
During references:
- cursor-on-Yul is resolved through
external_refsbefore normal AST span matching, - Yul usage locations are also appended back into the result set by matching
decl_id.
This is why references work inside inline assembly rather than only in high-level Solidity syntax.
Cross-file behavior: stable identity, not unstable node IDs
Node IDs are not stable across independent builds.
Cross-file references therefore do not share raw node IDs between builds.
Instead, the server derives a stable identity:
- declaration file absolute path (
def_abs_path) - declaration byte offset (
def_byte_offset)
Then each other cached build re-resolves that location locally (byte_to_id) and collects matching references in that build.
One important detail: resolution prefers name_location over src for declarations when available, so cross-file matching lands on the symbol name itself rather than a broader declaration span.
includeDeclaration behavior
includeDeclaration from the LSP request is honored directly:
- when
true, declaration location is included, - when
false, only usage locations are returned.
This flag is applied in both the current-build pass and cross-file passes.
What this implementation does not try to do
- It does not treat import string literals as references. Import path navigation is handled by go-to-definition logic.
- It does not guarantee cross-file completeness when another file has no cached build yet. Cross-file scanning only runs over available entries in
ast_cache.
Test coverage and confidence
Current tests give good coverage for the core reference architecture:
tests/cross_file_references.rscovers stable cross-file target resolution using(path, byte_offset).tests/rename.rsincludesgoto_references_cachedbehavior that relies onnameLocationfallback.tests/yul_external_references.rscovers Yul external-reference indexing and goto/reference mapping behavior.
This gives strong confidence in:
- target resolution correctness,
- cross-file re-resolution strategy,
- Yul assembly integration.
Recommended explicit additions
The following are good direct test additions for long-term safety:
lsp.rshandler-level test for full merge/dedup behavior across multiple cached builds.- a direct test for
includeDeclaration = falsethrough the full request path. - a mixed Solidity + Yul reference scenario validated end-to-end through the LSP method boundary.