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

Completions

What this page covers

This page explains the current completion implementation:

  • how the completion request is routed in lsp.rs,
  • what data structures power completion in completion.rs,
  • how dot-chain/type/member resolution works,
  • how auto-import tail candidates are appended,
  • what is covered by tests today.

Terms used in this page

  • CompletionCache: a set of hash maps/vectors built from compiler output and reused per request.
  • local cache: completion cache keyed by current URI (completion_cache[uri]).
  • root cache: project-level cache from root build (ast_cache[root_uri].completion_cache), used for global tail candidates.
  • general completions: non-dot completion items (keywords, globals, named symbols, units).
  • dot completions: member completions for expressions ending with ..
  • tail candidates: top-level importable symbols appended at the end of non-dot completion results (with optional import edits).

Request flow in lsp.rs

At textDocument/completion:

  • Read source text from text_cache (disk fallback when needed).
  • Load URI-local cache and root cache (if present).
  • Use local cache first, root cache as fallback.
  • Resolve file_id from path_to_file_id for scope-aware lookup.
  • For non-dot requests, build tail candidates from top-level importables.
  • Produce response via the completion handler and return.

Important behavior:

  • If trigger character is ., tail candidates are disabled.
  • If no cache is available, the server asynchronously hydrates from CachedBuild when possible.

The working data model

CompletionCache is intentionally map-heavy so request-time work is mostly lookups:

  • name_to_type: symbol name -> typeIdentifier
  • type_to_node: typeIdentifier -> declaration node id
  • node_members: declaration node id -> member completion items
  • method_identifiers: contract node id -> method signature completions (+ selector label details)
  • function_return_types: (contract_node_id, fn_name) -> return typeIdentifier (for foo().)
  • using_for: typeIdentifier -> extension methods
  • using_for_wildcard: methods from using X for *
  • scope_declarations, scope_parent, scope_ranges: scope-aware lookup context
  • linearized_base_contracts: inheritance traversal for scope resolution
  • top_level_importables_by_name, top_level_importables_by_file: import-on-completion support

This is built in build_completion_cache(...) from .sources AST and optional .contracts method identifiers.

Completion behavior by mode

Non-dot mode

Non-dot requests return prebuilt general completions and then append tail candidates (if any).

General completions include:

  • named AST symbols,
  • Solidity keywords,
  • magic globals,
  • units and type helpers.

Tail candidates are appended last so local/scope-aware symbols stay prioritized.

Dot mode

Dot requests parse the expression chain before the cursor (parse_dot_chain) and resolve segment-by-segment (get_chain_completions):

  • Plain segment: resolve symbol to type.
  • Call segment: use function_return_types to continue on return type.
  • Index segment: peel mapping/array value type and continue.

Final member set is composed from:

  • node_members,
  • method_identifiers,
  • using_for matches,
  • using_for_wildcard.

Scope-aware name resolution

When resolving a symbol in context, completion walks:

  • Start at the innermost scope from (file_id, byte position).
  • Walk outward through scope_parent.
  • Include inherited contracts via linearized_base_contracts.
  • Fall back to global maps (name_to_type, name_to_node_id) when needed.

This is why completion can prioritize locals/params while still finding inherited members.

Auto-import tail candidates

For non-dot mode, root cache can provide importable top-level symbols not declared in the current file.

Each tail candidate can carry additionalTextEdits to insert an import when selected.
Candidate extraction intentionally only includes directly declared top-level importables (contracts, structs, enums, UDVTs, free functions, constants) and excludes re-export aliases.

Why method identifiers are separate

AST member lists are useful but do not always carry full external signature detail in completion shape.
method_identifiers from .contracts adds:

  • canonical signature text,
  • selector metadata in label details,
  • better external/public method display for contract and interface completions.

Current limitations / tradeoffs

  • Completion quality depends on cache freshness; background cache hydration is best-effort.
  • Dot-chain resolution for very complex expressions is intentionally heuristic, not a full type-check pass.
  • Tail candidate import edits are only added in non-dot mode by design.

Test coverage and confidence

tests/completion.rs is extensive and covers:

  • scope declarations and scope parent behavior,
  • AST extraction of declarations by kind,
  • inheritance-aware scope resolution,
  • type parsing helpers (extract_node_id_from_type, mapping value extraction),
  • dot-chain parsing and chain resolution behavior,
  • using-for and wildcard method inclusion,
  • top-level importable extraction and tail-candidate behavior.

This gives strong confidence in cache construction and request-time resolution logic.

Recommended explicit additions

High-value direct additions:

  • request-level test in lsp.rs validating local-cache-first, root-fallback behavior,
  • request-level test asserting tail candidates are suppressed on . trigger,
  • end-to-end test validating additionalTextEdits import insertion behavior through the completion response.