Language server
decl-lsp speaks the Language Server Protocol over standard input and
output. It runs the same loader, checker, inference, and engine as the
decl command line — nothing it shows comes from a second analysis —
and every implementation ships one, answering the same scripted editor
session with the same messages (tests/parity/differential.py). This
document is informative: it specifies what the server offers, capability
by capability, at the level of rust-analyzer where that level applies.
Delivery order is the roadmap’s (Phase 6); today’s server provides the
diagnostics, hover, and definition of §3, §4, and §6.
1. Setup
Section titled “1. Setup”Point an LSP client at the decl-lsp binary for the decl language
(file extension .decl); the workspace root is the directory holding
decl.toml when there is one, else the file’s directory.
decl-lsp --versionprintsdecl-lsp <version>, the same version asdecl --versionand theserverInfoofinitialize; anything else on the command line is ignored and the server speaks the protocol on stdio.- VS Code and Zed: the extensions, 04. Editor extensions
— VS Code’s bundles this server and manages it, Zed’s finds or
downloads it; a generic LSP client works too (
command: decl-lsp,languageId: decl). - Neovim:
vim.lsp.start({ name = 'decl', cmd = { 'decl-lsp' }, root_dir = vim.fs.root(0, { 'decl.toml', '.git' }) })from aFileType declautocommand —extension/neovim/init.lua. - Helix: a
[[language]]entry withlanguage-servers = ["decl-lsp"]and[language-server.decl-lsp] command = "decl-lsp"—extension/helix/languages.toml. - Emacs: eglot, registered by
extension/emacs/decl-mode.el; Vim: the yegappan/lsp plugin (extension/vim/README.md); Sublime Text: the LSP package, registered byextension/sublime/LSP.sublime-settings.
Syntax highlighting comes from the grammar (tree-sitter-decl/, with
queries/highlights.scm), which editors with tree-sitter support load
directly; the server adds a semantic layer on top (§13).
2. Principles
Section titled “2. Principles”- One analysis. Every answer comes from the checker, inference, and engine of the command line, over the same universe (entry, imports, package) the command line opens; the server keeps a session object per package — the REPL’s (02_repl.md §1) — whose dependency tracking makes re-analysis incremental: a change re-checks its module and re-evaluates only what read it, and the result is observationally identical to a full pass (§9.4).
- Open buffers override the disk for every universe they take part in; a file the editor has not opened is read when needed and watched for changes (§14).
- Positions are exact. Every AST node carries its source range, and the checker records for every expression node its inferred type and the declaration a name resolves to, so any position answers “what is this, what type has it, where is it declared”.
- Messages are handled in order (a client may pipe
initializeandexitback to back), and the server exits when its input closes.
3. Diagnostics
Section titled “3. Diagnostics”textDocument/publishDiagnostics on open, on every change, and after
evaluation:
- Syntax errors positioned on the offending token; static
diagnostics (§12’s E3xxx/E4xxx) on the exact range of the construct
they concern; severities
error,warning,information, andhintfrom the diagnostic’s own. - Evaluation diagnostics — binding, assertions, dangling references, evaluation errors — on save or after an idle delay, for the exported outputs and for the inputs whose documents the workspace configuration binds, each at the literal entry that produced the value (or at the root when it came from a document).
- Root causes (§6.6) as
relatedInformation: an invalid value points at the diagnostic that invalidated it; an assertion failure lists the members its condition read. - Advisory findings (severity hint): an unused
import,const,func, or non-exported type; a?.whose left side is definitely present; a restatement that repeats the computed value; a derived member read only by its siblings and constraints, never navigated from outside (a candidate forx$). - Diagnostics carry their code (
E4050) and theType.assertid, and the quick fix for the code when one exists (§10).
4. Hover
Section titled “4. Hover”- An expression: its inferred static type, the absence flag when it is maybe-absent, and — when the position lies in an evaluated root — its evaluated value.
- A member declaration: its kind (required / optional / defaulted /
derived / hidden), type, default or expression, documentation (
///,@doc), and for a member of an output its evaluated value. - A type: its declaration and documentation, the
elsediagnostic it carries, the arms and discriminant of a union, the parent of an extension, the layers of an intersection. - A
std.*function: its signature and semantics line (§13). A unit literal: dimension, factor, and base-unit value. A dimension or unit declaration: its definition. - A context variable: the declared bound (
$parent: ref<…>) and the embedding site that satisfies it.$referrers(T, "m"): the edge it queries and the referrers’ declaration. - An import: the resolved module path (or package and version) and the declaration it names.
5. Completion and signature help
Section titled “5. Completion and signature help”- Members after
.and?.from the static type — hidden members asx$— with kind and type in the detail; on a union, the members every arm declares. - Context variables where they are meaningful (inside a record’s member
expressions);
$referrers(with the types that carry arefto the enclosing type, then their referencing members. std.namespaces and functions with signatures; type names, generic parameters, dimensions inquantity<…>; unit symbols after a number literal; the literal keywords.- Import completion: the names a module exports, the modules a package resolves, the packages the lock holds.
- Snippets: a record literal from the expected type with its required
members as placeholders;
assert name: …;when … { }; adiagnosticdeclaration. - Signature help for
funcandstd.*calls with the active parameter highlighted.
6. Navigation
Section titled “6. Navigation”- Definition for every name: declarations; members from a
navigation, an object key, an override, or a
$referrersmember string; context declarations; imports one hop and through re-exports; units and dimensions. - Type definition: from a member or expression to the declaration of
its type; from
ref<T>toT; from a value in a discriminated union to the arm it selects. - Declaration of a member: the type (or parent type) that declares it, when the member at the cursor is inherited.
- References across the universe (every module the workspace’s
packages resolve), with
documentHighlightfor the occurrences in the open buffer.
7. Symbols and hierarchies
Section titled “7. Symbols and hierarchies”- Document symbols: an outline of the module — declarations with
their members and
assertnames, context declarations,whengroups. - Workspace symbols: declarations across every module, searchable by name.
- Type hierarchy: supertypes and subtypes along extension chains
(
Parent {…}), intersection layers, and union arms. - Call hierarchy for
func: callers and callees, from the reference graph of §5.3. - Referrer hierarchy for
refmembers: which types reference which through which member — the graph$referrersanswers over.
8. Rename and linked editing
Section titled “8. Rename and linked editing”- Rename with
prepareRename: declarations, members (with every navigation, object key, override, and$referrersstring that names them), imports and theirasnames, units. It refuses a rename that would shadow (D27) or collide within a record’s single name space (D19), and it renames aType.assertid with its assert. - Linked editing: a local variable — a comprehension variable, a
lambda parameter, a match arm’s variable, a function parameter — with
its uses in its scope, so retyping the binding retypes the uses;
rename covers the same locals. (A map key and the
namerestating it, and a type with the diagnostic id derived from it, are candidates for later.)
9. Formatting, folding, and typing
Section titled “9. Formatting, folding, and typing”textDocument/formattingby the canonical formatter; no range formatting (the formatter keeps the author’s line structure).- Folding ranges for records, arrays, comprehensions,
whengroups,matcharms, and comment blocks; selection ranges that expand from a token through the syntax tree. - Typing assists (
onTypeFormatting): a continuation line indented by the §2.9 rule,///continued on Enter, brackets closed.
10. Code actions
Section titled “10. Code actions”Quick fixes, keyed to the diagnostic’s code:
| Code | Fix |
|---|---|
| E3003 unknown name | add the import from the workspace module that exports it, or qualify a namespace import |
| E4003 undeclared member | declare it on the type with the supplied value’s inferred type, or quote a key |
| E4013 union not discriminable | add a literal-typed member to the arms |
| E4030 / E4032 override | change the override’s type or kind to the parent’s |
| E4050 absence discipline | insert ?., ?? …, or an in guard |
| E4094 context variable undeclared | declare $parent: ref<…>, $key: …, or $root: ref<…> with the bound inferred from the uses |
D16 precedence (a && b ?? c) |
parenthesize |
non-exhaustive match |
add the missing arms |
const in a record body; x: T = e a document supplies |
x = e; x?: T = e |
Assists, on a selection or position:
- Extract an expression into a derived member
x = e, a hidden memberx$ = e, a moduleconst(when it is a constant expression), or afuncwith the free names as parameters; inline the reverse. - Convert derived ↔ hidden, defaulted ↔ derived, optional ↔ required;
an inline record type ↔ a named
type; an inlineelse error↔ adiagnosticdeclaration; anifchain over a discriminant ↔match; a unit literal to another unit of its dimension. - Generate an
outputskeleton for a type (required members as placeholders), aninputfor a type,exportfor a declaration, and the@expect-phase/@expect-errorheader of a fixture from its current diagnostics. - Fill the missing required members of a literal, or every member of a literal from its type.
- Reorder members into the canonical order (required, optional, defaulted, derived, hidden, constraints); flip the operands of a comparison.
11. Inlay hints
Section titled “11. Inlay hints”Each hint is a setting, off or on:
- the inferred type of a derived member without an annotation;
- parameter names at
funcandstd.*call sites; - the evaluated value of a defaulted or derived member of an output, at the end of its line;
- the base-unit value of a quantity literal (
250ms→0.25 s); - the bound of a context variable at its use, and the
$keytype of a map entry’s type; - closing-brace hints for long records (
} // Service).
12. Code lenses, commands, virtual documents
Section titled “12. Code lenses, commands, virtual documents”- On an
output: evaluate — opens its document in a live-updating virtual buffer (decl-evaluate://…) — and references. - On an
input: validate with … — binds a document from the workspace configuration or a file picker and shows its diagnostics. - On a type: the count of references, and instances — the outputs and inputs whose values carry it.
- On a fixture (
@expect-*header): run, judging it asdecl validatewould; on a corpus directory, the whole judgment. - Commands (
workspace/executeCommand):decl.evaluate,decl.validate,decl.bindInput,decl.trace(a path’s derivation or root cause, as the REPL’s:trace),decl.openRepl(a REPL attached to the workspace’s session object),decl.showSyntaxTree(the tree-sitter tree of the buffer),decl.reloadWorkspace.decl.evaluatewith a root answers{ root, document, diagnostics }— the document in canonical JSON — and, when the root’s@renderdeclares a form (05_render.md §8),rendered:{ kind: "json" | "yaml" | "text", text },{ kind: "files", files: [{ path, text }] }for a fan-out root, or{ kind: "error", diagnostic }.
13. Semantic tokens
Section titled “13. Semantic tokens”A layer over the grammar’s highlights, carrying what the syntax tree
cannot know: a keyword used as a member name (D33); a member’s kind —
required, optional, defaulted, derived, hidden — as modifiers; the kind
of a context variable; a unit symbol versus an identifier; an unresolved
name; a $referrers member string as a member reference. Full, range,
and delta requests.
14. Workspace
Section titled “14. Workspace”- One session object per package (
decl.toml), multi-root workspaces supported; a file outside any package is its own universe. workspace/didChangeWatchedFilesfor.declfiles,decl.toml,decl.lock, and the bound input documents; a change re-validates what read it.workspace/didChangeConfiguration: the input bindings for evaluation diagnostics, the inlay-hint switches, the idle delay.$/progressfor loading and evaluating a large universe; a status item with the universe’s size and the last evaluation’s time. For a client that advertiseswindow.workDoneProgress, an evaluation is wrapped in awindow/workDoneProgress/createrequest — its id an integer, the form every client accepts — andbegin/endnotifications; the client’s response to that request is consumed, not answered (the parity harness checks both).
15. Protocol summary
Section titled “15. Protocol summary”| Request or notification | Section |
|---|---|
initialize, shutdown, exit |
§2 |
textDocument/didOpen / didChange / didSave / didClose |
§2 |
textDocument/publishDiagnostics |
§3 |
textDocument/hover |
§4 |
textDocument/completion, completionItem/resolve, textDocument/signatureHelp |
§5 |
textDocument/definition, typeDefinition, declaration, references, documentHighlight |
§6 |
textDocument/documentSymbol, workspace/symbol, typeHierarchy/*, callHierarchy/* |
§7 |
textDocument/prepareRename, rename, linkedEditingRange |
§8 |
textDocument/formatting, foldingRange, selectionRange, onTypeFormatting |
§9 |
textDocument/codeAction, codeAction/resolve |
§10 |
textDocument/inlayHint, inlayHint/resolve |
§11 |
textDocument/codeLens, workspace/executeCommand |
§12 |
textDocument/semanticTokens/full / range / delta |
§13 |
workspace/didChangeWatchedFiles, didChangeConfiguration, $/progress |
§14 |
16. Not applicable
Section titled “16. Not applicable”rust-analyzer features with no counterpart in Decl: macro expansion,
lifetime and borrow-check hints, trait implementations, cargo
integration (build, run, debug), structural search and replace, memory
layout hovers, the debugger. There is no execution to step through:
evaluation is one deterministic function of the sources and the
documents, and :trace / decl.trace explains a value after the fact.
17. Status
Section titled “17. Status”Delivered (2026-09-04), in the three implementations and the parity
harness’s scripted session: positioned diagnostics — parse errors,
loading, static checks anchored to the declaration or expression they
concern, evaluation diagnostics anchored to the literal their path leads
to (§3); hover with the declaration, its documentation, and the inferred
type (§4); completion over the session’s engine (§5); definition, type
definition, references, and document highlights, imports followed (§6);
document symbols with record members (§7); prepare/rename across the
universe (§8); formatting and folding (§9); lenses on outputs and inputs
and the commands decl.evaluate, decl.validate, decl.trace,
decl.reloadWorkspace (§12); the input bindings by configuration (§14);
then signature help and workspace symbols (§5, §7), selection ranges
(§9), semantic tokens with member kinds and unresolved names (§13),
inlay hints — inferred types, parameter names, base-unit values (§11) —
the call and type hierarchies (§7), the quick fixes import the name
and add the missing member and the assist annotate (§10), and
decl.showSyntaxTree (§12); then linked editing and rename of local
variables (§8), the value hints (§11), the quick fixes qualify as
ns.X, declare X on T, use ?., supply a fallback with ??,
parenthesize the ?? expression, add the missing arms, and the
assists — the member-kind conversions, export, generate an output /
an input, the fixture header, fill the required members, flip the
comparison, extract to a constant / a derived member (§10) — and
error replies for a request whose handler fails; then the quick fixes
declare $x on T / declare $x as refkind (E4013), make
T.m defaulted (E4005), the assists inline (a constant), extract to a
named type, convert to the base unit, reorder the members
canonically, and the context-variable hints (§11). The server’s core
is transport-neutral (lsp-core.ts): stdio under Node, a web worker in
the browser (§1). Then on-type formatting (§9), the conversions declare
a diagnostic / inline the diagnostic and convert to match /
convert to if over a discriminant, inline a member (§10), and
$/progress around an analysis for a client that accepts it (§14).
Every item of §3–§14 is delivered except the virtual documents beyond
the syntax tree, which the extensions render on the client side.
18. Verification
Section titled “18. Verification”The session corpus (tests/lsp/, one session per capability: diagnostics,
hover and navigation, completion and signature help, symbols and
hierarchies, rename and linked editing, formatting, the quick fixes, the
assists, the conversions, hints, lenses, and commands, the progress
contract) holds the answers the reference gives, reviewed; every
implementation’s suite replays every session against its transcript, and
the parity harness replays every session over the three servers and
requires identical answers: every request kind of §15 with at least one
position per kind of answer, every quick fix and assist with a before
and after, every lens with its command; a session with an edit checks
that the incremental analysis answers as a fresh server would. Smoke in
Neovim and Helix (extension/smoke-editors.sh) and in VS Code on the
three benchmark examples closes each delivery.
© 2026 Luuvish. Decl is open source under the MIT License.
Type: Literata and IBM Plex, under the SIL Open Font License. Built with Astro Starlight.