Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Implementation

Overview

The textDocument/implementation LSP method navigates from an interface function declaration to its concrete implementation(s), and vice versa. In Solidity this is most commonly used for:

  • Interface → Implementation: cursor on IPoolManager.swap → jumps to PoolManager.swap
  • Implementation → Interface: cursor on PoolManager.swap → jumps to IPoolManager.swap

The server advertises implementationProvider: true in its capabilities.

Editor usage

Neovim

keymap("gri", vim.lsp.buf.implementation, "[G]oto [I]mplementation")

VS Code

Right-click a function → Go to Implementations (or Ctrl+F12).

Other editors

Any editor that supports the LSP textDocument/implementation method will work.

How it works

The base_function_implementation index

The feature is powered by CachedBuild.base_function_implementation, a bidirectional HashMap<NodeId, Vec<NodeId>> that maps each function to its equivalent interface/implementation IDs.

Built from: NodeInfo.base_functions — the baseFunctions/baseModifiers arrays in solc's AST output. When solc compiles contract PoolManager is IPoolManager, each function in PoolManager that overrides an IPoolManager function has a baseFunctions array pointing to the interface function's node ID.

Bidirectional: build_base_function_implementation() processes every node's base_functions and inserts edges in both directions:

  • implementation_id → [interface_id, ...]
  • interface_id → [implementation_id, ...]

This means the index works regardless of which side the cursor is on.

Index availability

The index is populated on both fresh builds (CachedBuild::new()) and warm-loaded builds (from_reference_index()), because it only depends on the nodes index (which is always available). This means textDocument/implementation works immediately on warm start without waiting for a full recompile.

Runtime flow

  1. Resolve the node at the cursor position via byte_to_id()
  2. Follow referencedDeclaration if the cursor is on a reference (e.g., a call site), not a declaration
  3. Look up the resolved target ID in base_function_implementation across all builds:
    • File build (per-URI)
    • Project build (project-wide)
    • Sub-caches (library sub-projects)
  4. Collect unique implementation IDs
  5. Resolve each implementation ID to an LSP Location by searching all builds for the node
  6. Return as GotoImplementationResponse (single Location or array)

Relationship to other features

The base_function_implementation index is shared across three features:

FeatureHow it uses the index
textDocument/implementationDirect lookup: target ID → equivalent IDs → jump to locations
callHierarchy/incomingCallsExpands target IDs to include equivalents, so callers via interface-typed references are captured
textDocument/referencesExpands target to include equivalent function IDs, so references to IPoolManager.swap appear when querying PoolManager.swap

See Call Hierarchy and References for details.

Key files

FileRole
src/goto.rsCachedBuild.base_function_implementation field, build_base_function_implementation() constructor
src/lsp.rsgoto_implementation handler, implementationProvider capability
src/solc_ast/mod.rsDeclNode::base_functions(), baseFunctions/baseModifiers extraction from AST JSON
src/references.rsbyte_to_id(), id_to_location() — used by the handler to resolve positions

Known limitations

  • Same-build node IDs only: The handler looks up base_function_implementation using bare node IDs within each build. This is safe because the index is per-build. However, if an interface is in a sub-cache and the implementation is in the file build, the sub-cache's index won't contain the implementation ID (and vice versa). The handler searches all builds to compensate.
  • Abstract contracts: Functions in abstract contracts that override interface functions are included. The index does not distinguish between abstract and concrete implementations.
  • Multi-level inheritance: If C overrides B which overrides A.foo, the baseFunctions for C.foo points to A.foo (solc's linearized resolution). The index captures this correctly.