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

Inlay Hints

What this page covers

This page documents the current inlay-hint implementation:

  • parameter hints for calls and emits,
  • how live-buffer positions are kept accurate,
  • how settings filter hint kinds at request time.

Terms used in this page

  • HintIndex: HashMap<abs_path, HintLookup> prebuilt from AST at build-cache creation time.
  • HintLookup: public struct with two private lookup maps and two public methods (resolve_callsite_with_skip, resolve_callsite_param). Callers use the methods, not the maps directly.
    • by_offset (private): exact call_start_byte → CallSite map (best when AST offsets are fresh).
    • by_name (private): fallback (name, arg_count) → CallSite map (works when offsets drift).
  • CallSite: private struct with three fields: info: ParamInfo, name: String, decl_id: u64.
  • ParamInfo: private struct nested inside CallSite — holds names: Vec<String> (parameter names) and skip: usize.
  • ResolvedCallSite: public struct returned by resolve_callsite_param — holds param_name: String and decl_id: u64.
  • skip: number of leading params to skip for hint labels (1 for using-for library calls, 0 otherwise).
  • ConstructorIndex: HashMap<u64, ConstructorInfo> — built as an intermediate from decl_index and passed to build_hint_index(). Not stored on CachedBuild; discarded after the hint index is built.

Why this design exists

Parameter names come from compiler AST semantics, but cursor/argument positions must follow live edits in the editor.
A single source cannot solve both well.

So the implementation splits responsibilities:

  • AST snapshot (HintIndex) for semantic mapping,
  • tree-sitter on live buffer for real-time argument positions.

Runtime flow

In src/lsp.rs::inlay_hint:

  • Read source bytes for the requested URI.
  • Load the cached build snapshot.
  • Generate raw hints.
  • Filter parameter hints (InlayHintKind::PARAMETER) based on settings.
  • Return hints, or None if empty.

Inside inlay_hints(...):

How callsite mapping is built

CachedBuild::new() in goto.rs builds the hint index in two steps:

  1. build_constructor_index(&decl_index) — scans decl_index for contract declarations with constructors and builds ConstructorIndex (HashMap<contract_id, ConstructorInfo>). This is a temporary; it is not stored on CachedBuild.
  2. build_hint_index(sources, &decl_index, &constructor_index) — for each source file:
    • Walk call-like AST nodes (FunctionCall and EmitStatement).
    • Resolve declaration via referencedDeclaration.
    • Extract parameter metadata from typed declarations (decl_index).
    • For new ContractName(args) expressions, look up constructor info from constructor_index.
    • Store both exact-offset (by_offset) and name/arity fallback (by_name) entries in HintLookup.

ConstructorIndex is discarded after build_hint_index returns. The final HintIndex is stored on CachedBuild.hint_index.

This is why request-time hinting is mostly lookup work, not full AST recomputation.

Parameter hint behavior

Hints are emitted for:

  • normal function calls,
  • member calls,
  • emit statements,
  • constructor-style new Contract(args) when constructor info exists.

Special cases handled:

  • using-for calls: skip = 1 when receiver is implicit and arg count is smaller than param count.
  • named-arg struct constructors: skipped (names are already visible at call site).
  • stale offsets: fallback to (name, arg_count) map.

Refresh behavior

Inlay hint refresh is triggered asynchronously (tokio::spawn) in two places:

  • after successful build/update in on_change,
  • after did_change_configuration.

This avoids blocking request/diagnostic flow while still asking the client to re-request hints.

Known tradeoffs

  • Exact offset matching can drift after edits/formatting; fallback improves resilience but can be less precise for overloaded same-name/same-arity cases.
  • Request-time accuracy depends on HintIndex freshness from the latest successful cached build.
  • Filtering happens in lsp.rs, so inlay_hints(...) may generate more hints than ultimately returned.

Test coverage and confidence

src/inlay_hints.rs includes strong helper-level coverage:

  • tree-sitter call/event/name extraction,
  • call argument indexing and byte-position mapping,
  • new expression handling,
  • resolve_callsite_param behavior (including skip and bounds).

This gives good confidence in the core extraction and lookup mechanics.

Recommended explicit additions

Useful request-level additions:

  • end-to-end textDocument/inlayHint tests that validate settings filtering by kind,
  • stale-offset overload scenario test through request path,
  • configuration-change refresh behavior test (ensuring client refresh is triggered).