Language Server
The flint-lsp crate implements the Flint language server. It reuses the compiler’s frontend crates to provide real-time editor feedback without duplicating language logic.
For user-facing feature documentation and editor setup, see Language Server in the Tools section.
Architecture
The language server is a single-threaded, synchronous event loop that reads JSON-RPC messages from stdin and writes responses to stdout. It does not use an async runtime.
Editor flint lsp
│ │
│ ── initialize ──────────────► │
│ ◄── capabilities ──────────── │
│ ── initialized ─────────────► │
│ │
│ ── didOpen ─────────────────► │ lex + parse + check
│ ◄── publishDiagnostics ───── │
│ │
│ ── completion ──────────────► │ index lookup
│ ◄── completionList ────────── │
│ │
│ ── hover ───────────────────► │ index lookup
│ ◄── hover content ─────────── │
│ │
│ ── shutdown ────────────────► │
│ ── exit ────────────────────► │
Crate Dependencies
flint-lsp depends on the compiler’s frontend crates but not on flint-cli:
flint-lexerfor tokenizationflint-parserfor CST/AST construction and recoveryflint-syntaxfor AST types, token kinds, spans, andLineIndexflint-checkfor semantic analysis and theSymbolTableflint-diagnosticsfor diagnostic types and severity mappingflint-driverfor workspace resolution and the canonical formatterflint-manifestforflint.tomlloading
Module Layout
| Module | Responsibility |
|---|---|
transport | Content-Length framed JSON-RPC over stdio |
protocol | LSP data types (positions, ranges, capabilities, requests) |
session | Event loop, request dispatch, state management |
documents | Open document store with automatic lex/parse on change |
index | Workspace-wide symbol index built from AST walks |
completion | Context-aware completions (keywords, imports, locals, members) |
diagnostics | Syntax and semantic diagnostic mapping |
hover | Function signatures and doc comments |
definition | Cross-file go-to-definition |
references | Cross-file find-references |
rename | Cross-file rename |
signature_help | Parameter info at call sites |
symbols | Document and workspace symbol providers |
formatting | Document formatting via the flint fmt engine |
actions | Code actions (fix hints, add/remove imports) |
semantic_tokens | Token classification for rich highlighting |
inlay_hints | Type annotations and parameter name hints |
highlights | Document-level identifier highlighting |
folding | Folding ranges for blocks and declarations |
location | Byte offset to LSP position conversion utilities |
Document Model
The server keeps an in-memory store of open documents. Each document holds:
- The current source text
- A
LineIndexfor position conversion - The latest
LexedModule(tokens and trivia) - The latest
ParsedModule(CST, AST, recovery status, parse issues)
On every didOpen and didChange, the server re-lexes, re-parses, rebuilds the symbol index, and publishes diagnostics. Incremental document sync is used (the editor sends only the changed range, which the server splices into the existing text before re-parsing).
Diagnostic Pipeline
Diagnostics flow through two lanes:
-
Syntax lane (immediate): parse issues from the current document are mapped to LSP diagnostics and published right away.
-
Analysis lane (on change): the server runs the full compiler pipeline in-process on all open documents:
flint_check::Checker::check()for semantic errors (unresolved symbols, duplicate definitions, invalid signatures, missing entry points)flint_lower::Lowerer::lower()to produce HIR and FIRflint_lower::TypeChecker::check()for deep type-level errors (mutability violations, ownership and move errors, definite initialization, return-path completeness, exhaustive match, closure capture validity, MCU recursion policy)
The lowering and type-checking pass only runs when semantic checking succeeds, since it depends on a well-formed checked package.
Both lanes run synchronously after each document change. There is no background thread or debounce timer in the current implementation. Because the compiler is a set of library crates called in-process, every diagnostic the compiler can produce is available in the editor with no translation layer or subprocess overhead.
Symbol Index
The workspace index maintains a per-document index of:
- Top-level symbols (functions, structs, enums, traits, constants, statics, aliases) with their spans, visibility, rendered signatures, doc comments, parameters, and children (fields, variants, methods)
- Imports with their paths and bound names
- Impl method associations (which type each method belongs to)
The index is rebuilt from the AST on every document change. It powers completion, hover, go-to-definition, find-references, rename, document symbols, workspace symbols, semantic tokens, and inlay hints.
Completion Strategy
Completion is context-sensitive. The server checks the cursor position and dispatches to different strategies:
- Inside
useitems: complete module paths from the known package list - After
::: complete enum variants - Inside
{ }after a type name: complete struct fields - After
.: complete struct fields, impl methods, trait methods, and imported module symbols. The receiver’s type is resolved from explicit annotations, construct expressions (Point { ... }), function call return types, orselfinside impl blocks. - Default: keywords, top-level symbols, imported names, local bindings (parameters, let bindings, for-loop variables), and public symbols from other documents