Skip to content

About

Language Server Protocol (LSP) implementation for Compact smart contract language (Midnight network) – diagnostics, completion, hover, go-to-definition, references, rename, and more

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

compact-lsp

License: MIT CI Rust crates.io Downloads docs.rs

This project extends the Midnight Network with additional developer tooling.

⚠️ Note: This is an experimental project developed in my personal free time for educational purposes — primarily to learn how Language Server Protocol implementations work. While functional, it is not officially supported. Feedback and contributions are welcome!

Language Server Protocol implementation for the Compact smart contract language (Midnight network).

Supports Compact language 0.23.0 – 0.27.0 (toolchains 0.31.1 – 0.35.0). The Mainnet / Preprod / Preview pin is 0.31.1 (language 0.23.0); 0.34.0 and 0.35.0 (language 0.26 / 0.27, ledger 9) are supported for the upcoming network upgrade.

Features

Feature Description
Diagnostics Real-time syntax errors + compiler errors on save
Semantic Tokens Rich syntax highlighting (functions, types, parameters, etc.)
Completion Keywords, types, snippets, local and imported symbols
Hover Documentation for keywords, types, and symbols
Go to Definition Jump to symbol definitions (local and imported)
Find References Find all usages of a symbol (local and cross-file)
Rename Rename symbols across the workspace
Signature Help Parameter hints while typing function calls
Document Symbols Outline view (circuits, structs, enums, modules)
Formatting Whole-document and range (selection) formatting via format-compact
Folding Ranges Code folding for blocks and functions
Cross-file Errors Errors propagate to dependent files on save
Version-aware symbols Registry suggestions follow the detected compiler's language version
Cross-contract calls Completion, hover, go-to-definition, signature help, find-references, and rename for ref.circuit(...) — reached through struct fields (w.token.circuit(...)), call bases (make().circuit(...)), method-call bases (factory.create().circuit(...)), index bases (items[i].circuit(...)), fields on a method result (items.head().value.circuit(...)), and generic calls/fields (pick<T_Token>(m).circuit(...), Box<Token>.value.circuit(...)); contract implements type completion
Struct & enum members Find References and Rename for struct fields (w.balance) and enum variants (Phase.day), gated by the owner type (including imported structs/enums)
Selective imports import { Token, Wallet as W } from "./mod" binds symbols (including aliases) explicitly, so imported types resolve to the right file instead of the ambiguous by-name workspace scan

Cross-Project Support

Works with Compact's import system:

import "./Utils" prefix Utils_;

Utils_add(5, 5);  // Completion, hover, go-to-def, find refs, rename, signature help all work

Cross-Contract Calls

On ledger-9 toolchains, a contract reference (ref: Token) supports the full editor feature set on the circuits declared by its contract type:

contract Token {
  circuit transfer(to: Address, amount: Uint<64>): Boolean;
}

contract implements Token;                              // completion offers `Token`

circuit useIt(ref: Token, to: Address, amount: Uint<64>): Boolean {
  return ref.transfer(to, amount);                      // completion, hover, go-to-def, signature help
}

The reference's type is resolved syntactically: from a written annotation (ref: Token), through a local alias (type MyToken = Token;), or inferred from the initializer of a const (const t = make(); uses make's declared return type — including when make is defined in another file, whose scope decides what the return-type name means). The contract type itself may live in another file: import it (import "./Token"; or import "./Token" prefix T_;) and the features work across files — including when the defining file is not open, resolved from the workspace scan. Member-chain bases resolve through struct fields (w.token.transfer(...)), including nested (a.wallet.token.transfer(...)) and imported structs. Expression bases resolve too: make().transfer(...) uses the callee's return type and items[i].transfer(...) uses the container's element type (Map<K, V> → V, Vector<n, T> → T, …). Method-call bases resolve the receiver first, then the method's return type — a contract circuit's declared return (factory.create().transfer(...)) or a built-in method's return with generics substituted (tokens.lookup(k).transfer(...), where Map.lookup returns V). Fields on a method result also resolve (items.head().value.transfer(...): List<T>.head() → Maybe<T>, whose value field is T). Generic parameters are read from explicit call-site type arguments (circuit pick<T>(m: Maybe<T>): T used as pick<T_Token>(m) resolves to T_Token) and, as a superset of the compiler, inferred when an argument's type directly matches the parameter (circuit id<T>(x: T): T used as id(token) resolves to token's type); generic struct fields substitute (Box<Token>.value → Token). Find-references and rename follow a circuit across every file where a contract-typed reference uses it. contract implements requires language 0.26+.

Note on generics. Compact only supports type parameters on declarations — struct, type aliases, top-level circuit (functions), witness, and module. Contracts, contract circuits, and member (.method) calls cannot be generic, so there is no such thing as a generic "method" in the language. Generic calls require explicit type arguments (id<Uint<64>>(x)); the compiler does not infer them.

Known Limitations

Area Status
Code Actions Not implemented
Contract-reference typing Heuristic, not a type system: resolves parg / const / ledger annotations, follows type X = Y aliases, walks struct fields (w.token.circuit(...)), resolves call bases (make().circuit(...)), method-call bases (factory.create().circuit(...), tokens.lookup(k).circuit(...)), index bases (items[i].circuit(...)), and field access on a method result (items.head().value.circuit(...)), and infers const t = make(); from the callee's declared return type (same file, or an imported/workspace callee resolved in its own file scope). Generic parameters come from explicit call-site type arguments (first<Uint<64>>(m)) or, as a superset of the compiler, from an argument whose type directly matches the parameter (id<T>(x: T) as id(token)), and generic struct fields substitute (Box<Token>.value); generic methods are not a Compact construct

Requirements

  • Rust toolchain 1.90+ (for building; pinned in rust-toolchain.toml)
  • Compact devtools (compact) with a toolchain in 0.31.1 – 0.35.0 — install the one you target with compact update 0.31.1 (or 0.34.0 / 0.35.0). Provides compactc (diagnostics) and format-compact (formatting).

Version support

The LSP reads the compiler's language version and gates registry symbols so one registry serves both the mainnet compiler and newer toolchains:

Toolchain Language Ledger Deployable to public networks
0.31.1 0.23.0 8 ✅ Mainnet / Preprod / Preview (target)
0.34.0 0.26.0 9 ❌
0.35.0 0.27.0 9 ❌

Registry entries carry a min_language_version and, for symbols removed in a later language version, an exclusive max_language_version. One registry therefore serves both the mainnet compiler and newer toolchains: a symbol is hidden when the detected compiler is below its minimum (e.g. keccak256 on 0.23.0) or at/after a removal. When no compiler is found, gating is disabled and all symbols are shown. On toolchains where the ZKIR-v3 backend is opt-in (0.34.100 – 0.35.104), the LSP automatically passes --feature-zkir-v3 so the foreign-curve symbols resolve. New syntax is supported too: contract implements and emit(...). Upstream release notes: https://docs.midnight.network/relnotes/compact; full version map and migration notes: MIGRATION_COMPILER_0.31.1.md and CLAUDE.md.

Install

Install the language server from crates.io:

cargo install compact-lsp

Prebuilt binaries for Linux, macOS (Intel + Apple silicon), and Windows are on the releases page.

Or build from source:

cargo build --release
# binary at target/release/compact-lsp

Two crates are published:

Crate What it is Docs
compact-lsp the language server binary (cargo install compact-lsp) —
compact-analyzer reusable analysis library (diagnostics, formatter, parser) docs.rs

Compiler Location

The LSP auto-detects the compactc compiler:

  1. COMPACT_COMPILER environment variable
  2. Compact devtools active toolchain $COMPACT_DIRECTORY/bin/compactc (default ~/.compact/bin/compactc; the symlink compact update <ver> repoints) — resolved to the real compactc.bin
  3. ~/compactc/compactc.bin
  4. compactc.bin / compactc in PATH

The detected language version gates registry symbols: each entry carries a min_language_version and an exclusive max_language_version, so symbols added after 0.23.0 (e.g. keccak256) or removed in a later version are hidden appropriately. When no compiler is found, gating is disabled and all symbols are shown. On toolchains where the ZKIR-v3 backend is opt-in (0.34.100 – 0.35.104), the LSP passes --feature-zkir-v3 automatically. format-compact is found the same way (COMPACT_FORMATTER, then the devtools bin/, then PATH).

Development

cargo fmt --all --check
cargo clippy --all-targets --all-features -- -D warnings
cargo build --release
cargo test

GitHub Actions runs these on main and dev, plus a compiler-probe matrix over toolchains 0.31.1, 0.34.0, and 0.35.0 that installs each toolchain and runs the full suite with COMPACT_LSP_REQUIRE_COMPILER=1 / COMPACT_LSP_REQUIRE_FORMATTER=1 (a missing toolchain fails instead of skipping), and a report-only coverage job (cargo llvm-cov). The end-to-end LSP tests spawn the server binary, speak JSON-RPC over stdio, and are backed by compiler-validated fixture projects; see TESTING.md for the layers, snapshots, property tests, and known issues.

  • Testing — see TESTING.md for the test layers, fixtures, snapshots, and known issues.
  • Changelog — see CHANGELOG.md for release notes.
  • Releasing — see RELEASING.md; cut a release with scripts/release.sh.
  • Upstream tracking — see UPSTREAM_RELEASES.md; the weekly upstream-watch workflow files an issue when a newer Compact toolchain ships.

Related Projects

  • compact-tree-sitter - Tree-sitter grammar for Compact (published on crates.io as tree-sitter-compact)

License

MIT

About

Language Server Protocol (LSP) implementation for Compact smart contract language (Midnight network) – diagnostics, completion, hover, go-to-definition, references, rename, and more

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages