Crystal V2: experimental LSP-first compiler work, current status, and feedback request

Hi everyone,

I want to share an experimental Crystal project I have been working on:

The short version: it started as an attempt to build a better Crystal LSP, and

over time grew into a larger compiler experiment with a new frontend, HIR/MIR

pipeline, LLVM backend, and bootstrap work.

This is not an official Crystal replacement, and the compiler/codegen side is

still beta. The LSP server is currently the most usable and stable part.

Why I started with LSP

My original motivation was developer experience. Crystal is a great language,

but for larger projects the editor experience can still be rough: slow first

responses, incomplete navigation, hover/definition gaps, semantic coloring

issues, and expensive re-analysis after small changes.

So I started with the LSP problem:

  • fast hover and go-to-definition;

  • better source-backed signatures;

  • semantic tokens that work on real Crystal/stdlib code;

  • completion/signature help without loading too much of the dependency graph;

  • document symbols, folding, formatting, rename, references, inlay hints, and

call hierarchy;

  • VS Code integration that can run side by side with the existing crystal

compiler, using crystal2 tool lsp or a configured server path.

The LSP now has a focused regression suite. The latest local gate is:


spec/lsp: 266 examples, 0 failures

It has also been tested on real projects, including Crystal V2 itself and

DiamondDB.

What is different from existing Crystal LSPs?

The main goal is not just “another LSP server”, but a compiler-backed language

server with aggressive caching and fast foreground paths.

Some design points:

  • Lazy foreground work. Opening a file should not eagerly build every index

if the first user action only needs hover, definition, or document symbols.

  • AST/project/prelude caches. The server keeps reusable summaries and avoids

redoing unchanged work where possible.

  • Fast navigation paths. Many hover/definition requests avoid dependency

graph loading and use source-backed method/type information directly.

  • Semantic token optimization. Large semantic-token responses are cached,

persisted for unchanged disk-backed files, and support LSP full/delta

responses.

  • Real-world edge cases. Recent fixes cover Crystal operator methods,

generated numeric conversions, macro calls, lib fun receivers, scoped aliases,

qualified receivers, callable parameters, and semantic coloring inside

case/when branches.

  • VS Code configurability. The extension no longer hardcodes a repo-local

server path. It can discover crystal2, crystal_v2, or crystal_v2_lsp,

and the path/args can be overridden in settings.

This is still young software, but the current LSP is usable enough that I would

like other Crystal users to try it and report concrete failures.

Why did this turn into code generation?

While building a good LSP, I kept running into the same underlying need: the

language server wants compiler-quality parsing, name resolution, type

information, macro awareness, and stable source mapping.

At that point it became natural to ask: if the frontend and semantic model are

being rebuilt anyway, can we also experiment with a compiler architecture that

is easier to cache, inspect, test, and eventually bootstrap?

That led to the current compiler pipeline:


Crystal source

-> parser / arena AST

-> HIR

-> MIR

-> LLVM IR

-> native binary

The bigger long-term idea is a compiler architecture with:

  • better incremental and cached analysis;

  • explicit IR boundaries for debugging;

  • source/HIR/MIR/LLVM equivalence checks across bootstrap stages;

  • method-level reachability analysis;

  • room for memory-management experiments such as stack/slab/ARC/GC strategy

selection;

  • a shared compiler core that can power both codegen and the LSP.

Current compiler/codegen status

The honest status: the compiler is not ready as a production replacement.

What works:

  • the V2 compiler can be built from the current Crystal compiler;

  • stage1 can build a generated stage2 compiler;

  • many focused no-prelude/codegen/bootstrap regressions pass;

  • the repository has HIR/MIR/LLVM infrastructure and many regression guards;

  • there is a spec-first contract layer under docs/specs/ for bootstrap

invariants.

What does not work yet:

  • generated stage2 is still unstable on broader full-prelude compilation;

  • clean stage1 -> stage2 -> stage3 bootstrap is still in progress;

  • codegen has known bugs and should be treated as experimental;

  • it is not a drop-in replacement for the official Crystal compiler.

So my recommendation today is:

  • try the LSP if you want editor tooling;

  • look at the compiler/codegen side if you are interested in compiler

internals, experiments, or helping with bootstrap bugs.

Things I would like Crystal V2 to eventually enable

Some of these are already partly implemented; some are still design goals:

  • very fast editor feedback on large Crystal files;

  • reliable source-backed hover/definition/signature help;

  • better semantic coloring for Crystal-specific constructs and operators;

  • incremental compilation-style dependency tracking;

  • inspectable HIR/MIR/LLVM outputs for debugging compiler bugs;

  • a bootstrap corridor with normalized semantic equivalence checks:

original -> stage1 -> stage2 -> stage3 -> ...;

  • room to experiment with memory strategies beyond today’s default model;

  • better tooling hooks around formatting, symbols, call hierarchy, and later

debugging support.

How to try it

Repository:

Build the LSP server:


git clone https://github.com/skuznetsov/crystal_lsp

cd crystal_lsp

./build_lsp.sh

The VS Code extension is in:


vscode-extension/

It can use:


crystal2 tool lsp

or a manually configured server path:


{

"crystalv2.lsp.serverPath": "/path/to/crystal2",

"crystalv2.lsp.serverArgs": ["tool", "lsp"]

}

Useful docs:

Feedback wanted

The most useful feedback would be:

  • LSP bugs on real Crystal projects, especially hover/definition/completion

misses;

  • slow files or slow request traces;

  • semantic coloring gaps;

  • crashes with a small reproducer;

  • codegen/bootstrap contributors who enjoy reducing compiler edge cases.

If you try it, please include:

  • OS and Crystal version;

  • how you launched the server;

  • the smallest .cr snippet or repo/file where it fails;

  • what the current Crystal tooling does versus Crystal V2.

Again: this is experimental, and the compiler side is not production-ready.

But the LSP is now stable enough that I think it is worth broader testing.

3 Likes