Stream: beginners

Topic: `roc experimental-lsp` crashes; bug in LSP or my code?


view this post on Zulip Lukas Juhrich (Jan 09 2026 at 18:58):

I tried to get acquainted with the new compiler, new syntax, and the language in general. I've been able to run this (yay!) with roc build (@5f964dcd):

app [main!] { pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.5/BJBzo2SR2o5w3StmubGWvnPHq6hfePMaNWy5MwkPuZUs.tar.zst" }

import pf.Stdout
import pf.Arg exposing [Arg]

# main! : List Arg => Try {}
main! = |_args| {
    Stdout.line!("Hello, World!")
    dbg 5->calc()
    Stdout.line!("${calc(7).to_str()}")
    Ok({})
}

calc = |n| n + 1

trying to set up the LSP in helix however, I get a crash when viewing this file:

thread 27907 panic: Arrays out of sync:
 type_nodes=17
  region_nodes=38

/home/lukas/code/30software/roc/src/check/Check.zig:255:28: 0x4be3638 in copyVar (mod.zig)
            std.debug.panic(
                           ^
/home/lukas/code/30software/roc/src/check/Check.zig:1260:57: 0x4965ed8 in checkPlatformRequirements (mod.zig)
            const copied_required_var = try self.copyVar(required_type_var, platform_env, required_type.region);
                                                        ^
/home/lukas/code/30software/roc/src/compile/compile_build.zig:683:46: 0x4a76d82 in checkPlatformRequirements (mod.zig)
        try checker.checkPlatformRequirements(platform_root_env, &platform_to_app_idents);
                                             ^
/home/lukas/code/30software/roc/src/compile/compile_build.zig:586:43: 0x4a7bfa5 in build (mod.zig)
        try self.checkPlatformRequirements();
                                          ^
/home/lukas/code/30software/roc/src/lsp/syntax.zig:88:18: 0x4dfecec in check (mod.zig)
        env.build(absolute_path) catch |err| {
                 ^
/home/lukas/code/30software/roc/src/lsp/server.zig:236:63: 0x4e061d7 in runSyntaxCheck (mod.zig)
            const publish_sets = try self.syntax_checker.check(uri, if (doc) |d| d.text else null, root_path);
                                                              ^
/home/lukas/code/30software/roc/src/lsp/server.zig:225:32: 0x4e06bce in onDocumentChanged (mod.zig)
            self.runSyntaxCheck(uri) catch |err| {
                               ^
/home/lukas/code/30software/roc/src/lsp/handlers/did_open.zig:44:35: 0x4a2666d in call (mod.zig)
            self.onDocumentChanged(uri);
                                  ^
/home/lukas/code/30software/roc/src/lsp/server.zig:176:24: 0x4832b25 in handleNotification (mod.zig)
                handler(self, params) catch |err| {
                       ^
/home/lukas/code/30software/roc/src/lsp/server.zig:149:44: 0x4833850 in handlePayload (mod.zig)
                try self.handleNotification(method, obj.get(\params\"));\
                                           ^
/home/lukas/code/30software/roc/src/lsp/server.zig:114:31: 0x4833ec3 in processNextMessage (mod.zig)
            self.handlePayload(payload) catch |err| {
                              ^
/home/lukas/code/30software/roc/src/lsp/server.zig:100:47: 0x48341a4 in run (mod.zig)
            while (try self.processNextMessage()) {}
                                              ^
/home/lukas/code/30software/roc/src/lsp/server.zig:312:19: 0x4834b40 in runWithStdIo (mod.zig)
    try server.run();
                  ^
/home/lukas/code/30software/roc/src/lsp/mod.zig:10:28: 0x4834ffd in runWithStdIo (mod.zig)
    try server.runWithStdIo(allocator, debug);
                           ^
/home/lukas/code/30software/roc/src/cli/main.zig:766:61: 0x4893ac9 in mainArgs (main.zig)
        .experimental_lsp => |lsp_args| try lsp.runWithStdIo(allocs.gpa, .{
                                                            ^
/home/lukas/code/30software/roc/src/cli/main.zig:643:13: 0x4895408 in main (main.zig)
    mainArgs(&allocs, args) catch |err| {
            ^
/usr/lib/zig/std/start.zig:627:37: 0x4895a01 in main (std.zig)
            const result = root.main() catch |err| {
                                    ^
/usr/lib/zig/libc/musl/src/env/__libc_start_main.c:95:7: 0x9723e9b in libc_start_main_stage2 (/usr/lib/zig/libc/musl/src/env/__libc_start_main.c)
 exit(main(argc, argv, envp));
      ^
???:?:?: 0x9702149 in ??? (???)
Unwind error at address `exe:0x9702149` (error.MissingFDE), trace may be incomplete

This is the LSP communication to that point:

2026-01-09T19:36:48.612 helix_core::syntax [INFO] Skipping syntax config for 'roc' because the parser's shared library does not exist
2026-01-09T19:36:48.613 helix_lsp::transport [INFO] roc-lsp -> {"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{"general":{"positionEncodings":["utf-8","utf-32","utf-16"]},"textDocument":{"codeAction":{"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}},"dataSupport":true,"disabledSupport":true,"isPreferredSupport":true,"resolveSupport":{"properties":["edit","command"]}},"completion":{"completionItem":{"deprecatedSupport":true,"insertReplaceSupport":true,"resolveSupport":{"properties":["documentation","detail","additionalTextEdits"]},"snippetSupport":true,"tagSupport":{"valueSet":[1]}},"completionItemKind":{}},"formatting":{"dynamicRegistration":false},"hover":{"contentFormat":["markdown"]},"inlayHint":{"dynamicRegistration":false},"publishDiagnostics":{"tagSupport":{"valueSet":[1,2]},"versionSupport":true},"rename":{"dynamicRegistration":false,"honorsChangeAnnotations":false,"prepareSupport":true},"signatureHelp":{"signatureInformation":{"activeParameterSupport":true,"documentationFormat":["markdown"],"parameterInformation":{"labelOffsetSupport":true}}}},"window":{"workDoneProgress":true},"workspace":{"applyEdit":true,"configuration":true,"didChangeConfiguration":{"dynamicRegistration":false},"didChangeWatchedFiles":{"dynamicRegistration":true,"relativePatternSupport":false},"executeCommand":{"dynamicRegistration":false},"fileOperations":{"didRename":true,"willRename":true},"inlayHint":{"refreshSupport":false},"symbol":{"dynamicRegistration":false},"workspaceEdit":{"documentChanges":true,"failureHandling":"abort","normalizesLineEndings":false,"resourceOperations":["create","rename","delete"]},"workspaceFolders":true}},"clientInfo":{"name":"helix","version":"25.07.1"},"processId":27898,"rootPath":"/home/lukas/code/30software/roc","rootUri":"file:///home/lukas/code/30software/roc","workspaceFolders":[{"name":"roc","uri":"file:///home/lukas/code/30software/roc"}]},"id":0}
2026-01-09T19:36:48.619 helix_lsp::transport [ERROR] roc-lsp err <- "roc-lsp logging to /tmp/roc-lsp-debug.log\n"
2026-01-09T19:36:48.622 helix_lsp::transport [INFO] roc-lsp <- {"jsonrpc":"2.0","id":0,"result":{"capabilities":{"positionEncoding":"utf-16","textDocumentSync":{"openClose":true,"change":2}},"serverInfo":{"name":"roc-lsp","version":"0.1"}}}
2026-01-09T19:36:48.622 helix_lsp::transport [INFO] roc-lsp -> {"jsonrpc":"2.0","method":"initialized","params":{}}
2026-01-09T19:36:48.622 helix_lsp::transport [INFO] roc-lsp -> {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"roc","text":"app [main!] { pf: platform \"https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.5/BJBzo2SR2o5w3StmubGWvnPHq6hfePMaNWy5MwkPuZUs.tar.zst\" }\n\nimport pf.Stdout\nimport pf.Arg exposing [Arg]\n\n# main! : List Arg => Try {}\nmain! = |_args| {\n\tStdout.line!(\"Hello, World!\")\n\tdbg 5->calc()\n\tStdout.line!(\"${calc(7).to_str()}\")\n\tOk({})\n}\n\ncalc = |n| n + 1\n","uri":"file:///home/lukas/code/30software/roc/test.roc","version":0}}}
2026-01-09T19:36:48.836 helix_lsp::transport [ERROR] roc-lsp err <- "thread 27907 panic: Arrays out of sync:\n"
[…rest of above stack trace]

Is this a bug in the LSP, nonsense in my attempt at writing roc, or do I need to do some special setup to make the LSP work?

If the first, how can I debug it further?
Thanks in advance :)

view this post on Zulip Luke Boswell (Jan 09 2026 at 20:30):

I noticed your using version 0.5 of the platform, does it still happen with the latest which I think is 0.6?

view this post on Zulip Luke Boswell (Jan 09 2026 at 20:31):

Either way you have definitely found a bug, if you wouldn't mind making a GH issue that'd be good :grinning:

view this post on Zulip Lukas Juhrich (Jan 09 2026 at 20:33):

Alrighty, will check with 0.6. thanks for the quick reaction. :)
Before reporting I'll move around a bit in gdb which I finally managed to set up

view this post on Zulip Lukas Juhrich (Jan 09 2026 at 20:37):

Yep, same behavior with

app [main!] { pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.6/2BfGn4M9uWJNhDVeMghGeXNVDFijMfPsmmVeo6M4QjKX.tar.zst" }

(note to self: timestamp 2026-01-09T21:36:45.221 in helix logs)

view this post on Zulip Luke Boswell (Jan 09 2026 at 20:38):

I know that feature is super early. We haven't touched it in a while and I don't think we ever got any integration tests for it, so it definitely needs some love.

view this post on Zulip Luke Boswell (Jan 09 2026 at 20:40):

Though that bug is deep in Check which should be quite reliable so I'm guessing some simple is broken here, like how its wired up to the existing compiler pipeline or something.

view this post on Zulip Lukas Juhrich (Jan 09 2026 at 20:40):

No worries, I got that from the experimental. I figured it might be in a „it works but only on tuesdays and for even-length input messages“ kinda situation where I just would've had to know what knobs to turn. happy to report anything

view this post on Zulip Lukas Juhrich (Jan 11 2026 at 19:06):

Wanted to give debugging this myself a shot (via zig build roc && ./zig-out/bin/roc experimental-lsp < crash.lsp), but I struggle in two orthogonal ways:

  1. gdb gets symbols (i.e., correctly shows function names in bt), but has no line info. Am I missing gdb config or a build flag?
  2. building roc takes quite a bit of time, e.g. addidng a self.debugAssertArraysInSync() somewhere in Check.zig triggers a rebuild taking ~1m25s on my machine. That's a bit too long of a loop for printf debugging. A big chunk of that seems to be building interpreter shims for all sorts of platforms (arm, wasm, …) and linking in general. Is there any way I can restrict what gets build, are there any other knobs I can turn, or is my machine just too slow (i7-8650U)? I haven't found any cross-compilation related build flag in zig build --help.

Perhaps I'm missing an obviously better workflow here.

view this post on Zulip Lukas Juhrich (Jan 11 2026 at 19:19):

To give some detail regarding the missing line info: zig itself properly reports lines in the panic:

 ./zig-out/bin/roc experimental-lsp --debug-build --debug-transport --debug-syntax --debug-server < crash.lsp
roc-lsp logging to /tmp/roc-lsp-debug.log
Content-Length: 177

{"jsonrpc":"2.0","id":0,"result":{"capabilities":{"positionEncoding":"utf-16","textDocumentSync":{"openClose":true,"change":2}},"serverInfo":{"name":"roc-lsp","version":"0.1"}}}thread 203073 panic: [PRE check platform reqs] Arrays out of sync:
 type_nodes=0
  region_nodes=38

/home/lukas/code/30software/roc/src/check/Check.zig:255:28: 0x4965751 in checkPlatformRequirements (mod.zig)
            std.debug.panic(
                           ^
[…]

but when I start this via gdb ./zig-out/bin/roc, I get:

Reading symbols from ./zig-out/bin/roc...
(gdb) run experimental-lsp < crash.lsp
Starting program: /home/lukas/code/30software/roc/zig-out/bin/roc experimental-lsp < crash.lsp
[New LWP 203790]
[LWP 203790 exited]
Content-Length: 177

{"jsonrpc":"2.0","id":0,"result":{"capabilities":{"positionEncoding":"utf-16","textDocumentSync":{"openClose":true,"change":2}},"serverInfo":{"name":"roc-lsp","version":"0.1"}}}thread 203685 panic: [PRE check platform reqs] Arrays out of sync:
 type_nodes=0
  region_nodes=38

[…backtrace from zig panic…]
(gdb) f 7
#7  0x0000000004965752 in Check.checkPlatformRequirements (self=0x7ffffffece60, platform_env=0x7ffff62e4f20, platform_to_app_idents=0x7ffffffee1d8)
(gdb) list
39      }
40
41      void __restore_sigs(void *set)
42      {
43              __syscall(SYS_rt_sigprocmask, SIG_SETMASK, set, 0, _NSIG/8);
44      }

which is clearly not the source. I don't know how this is supposed to work under the hood, I'm just used to it „just working“ in the gcc -g world.

view this post on Zulip Lukas Juhrich (Jan 11 2026 at 19:44):

Luke Boswell said:

Either way you have definitely found a bug, if you wouldn't mind making a GH issue that'd be good :grinning:

https://github.com/roc-lang/roc/issues/8995 now exists

view this post on Zulip Anton (Jan 12 2026 at 14:04):

Hi @Lukas Juhrich, looks like Richard fixed your issue :)
We rarely use gdb so I'm not sure what's going wrong there.

A big chunk of that seems to be building interpreter shims for all sorts of platforms (arm, wasm, …)

Interesting, do you happen to know if this is necessary @Luke Boswell?

view this post on Zulip Luke Boswell (Jan 12 2026 at 19:56):

Yes it's necessary to build the roc cli. Until we have the llvm backend we currently pre-built the interpreter into a shim and use that for roc build.

view this post on Zulip Luke Boswell (Jan 12 2026 at 19:57):

So instead of linking the app which has been lowered to an object file with the host, we give the app in a serialised IR to our interpreter shim, and the platform host doesn't see anything different from a real compiled app.

view this post on Zulip Lukas Juhrich (Jan 12 2026 at 21:49):

Luke Boswell said:

So instead of linking the app which has been lowered to an object file with the host, we give the app in a serialised IR to our interpreter shim

Oh, so because I can do cross compilation a la roc build --target=<something else>, by default we ship all the other shims, because I might build for arm even though I am on x86_64? Pretty cool that roc build supports cross compilation ootb like that.

However, but wouldn't it technically be possible to disable the cross compilation targets and just not build the n-1 other shims?

view this post on Zulip Luke Boswell (Jan 12 2026 at 22:31):

Yeah we could not build these and embed them in the cli binary -- which would disable roc build. Or only build for the current native machine and embed just that -- which would prevent cross-compilation.

If it's not a major issue -- I'd prefer to leave it as is for now, as we are close enough to having an LLVM backend and removing all these shims anyway.

view this post on Zulip Richard Feldman (Jan 12 2026 at 22:33):

I very strongly think we should consider cross compilation mandatory

view this post on Zulip Luke Boswell (Jan 12 2026 at 22:34):

Yeah I was just thinking some kind of debug build flag here to speed up the loop for debugging things

view this post on Zulip Richard Feldman (Jan 12 2026 at 22:34):

it's a major selling point of Go, and we've done a lot of work to make it possible; it would be a real shame to miss out on it at the very end by not including a few kilobytes of shims :smile:

view this post on Zulip Richard Feldman (Jan 12 2026 at 22:35):

oh sure, for debugging it's fine :+1:

view this post on Zulip Lukas Juhrich (Jan 13 2026 at 06:59):

Oh, of course, I was only talking about a local debug build and trying to understand things, not suggesting anything else. Thanks for all the clarification!


Last updated: Feb 20 2026 at 12:27 UTC