zls stuck with 100% CPU on a bad `const bar = if (...) else null;` statement (textDocument_hover)
Zig Version
0.13.0
ZLS Version
0.13.0
Client / Code Editor / Extensions
NVIM v0.10.3
Steps to Reproduce and Observed Behavior
Following code snippet will make zls hang, consuming 100% CPU:
const OPTIONAL_STR = ?[]const u8;
const OPTIONAL_USIZE = ?usize;
const AN_USIZE = usize;
const FOO = OPTIONAL_USIZE orelse @max(1, AN_USIZE);
// ,-- Gets stuck here.
// vvv
const BAR = if (OPTIONAL_STR == null) {
(OPTIONAL_USIZE orelse (FOO + 1) / 2);
} else null;
When I try to use Hover Documentation, neovim just gets stuck, and in htop I can see zls using 100% of CPU cycles.
Expected Behavior
Documentation shows up in expected time. (The type is most probably unknown in this case.)
Relevant log output
[START][2025-02-07 15:43:38] LSP logging initiated
[INFO][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:731 "Starting RPC client" { cmd = { "/home/netvor/.local/share/nvim/mason/bin/zls" }, extra = { cwd = "/home/netvor" }}
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:286 "rpc.send" { id = 1, jsonrpc = "2.0", method = "initialize", params = { capabilities = { general = { positionEncodings = { "utf-16" } }, textDocument = { callHierarchy = { dynamicRegistration = false }, codeAction = { codeActionLiteralSupport = { codeActionKind = { valueSet = { "", "quickfix", "refactor", "refactor.extract", "refactor.inline", "refactor.rewrite", "source", "source.organizeImports" } } }, dataSupport = true, dynamicRegistration = true, isPreferredSupport = true, resolveSupport = { properties = { "edit" } } }, completion = { completionItem = { commitCharactersSupport = true, deprecatedSupport = true, documentationFormat = { "markdown", "plaintext" }, insertReplaceSupport = true, insertTextModeSupport = { valueSet = { 1, 2 } }, labelDetailsSupport = true, preselectSupport = true, resolveSupport = { properties = { "documentation", "detail", "additionalTextEdits", "sortText", "filterText", "insertText", "textEdit", "insertTextFormat", "insertTextMode" } }, snippetSupport = true, tagSupport = { valueSet = { 1 } } }, completionItemKind = { valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 } }, completionList = { itemDefaults = { "commitCharacters", "editRange", "insertTextFormat", "insertTextMode", "data" } }, contextSupport = true, dynamicRegistration = false, insertTextMode = 1 }, declaration = { linkSupport = true }, definition = { dynamicRegistration = true, linkSupport = true }, diagnostic = { dynamicRegistration = false }, documentHighlight = { dynamicRegistration = false }, documentSymbol = { dynamicRegistration = false, hierarchicalDocumentSymbolSupport = true, symbolKind = { valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 } } }, formatting = { dynamicRegistration = true }, hover = { contentFormat = { "markdown", "plaintext" }, dynamicRegistration = true }, implementation = { linkSupport = true }, inlayHint = { dynamicRegistration = true, resolveSupport = { properties = { "textEdits", "tooltip", "location", "command" } } }, publishDiagnostics = { dataSupport = true, relatedInformation = true, tagSupport = { valueSet = { 1, 2 } } }, rangeFormatting = { dynamicRegistration = true }, references = { dynamicRegistration = false }, rename = { dynamicRegistration = true, prepareSupport = true }, semanticTokens = { augmentsSyntaxTokens = true, dynamicRegistration = false, formats = { "relative" }, multilineTokenSupport = false, overlappingTokenSupport = true, requests = { full = { delta = true }, range = false }, serverCancelSupport = false, tokenModifiers = { "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary" }, tokenTypes = { "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator", "decorator" } }, signatureHelp = { dynamicRegistration = false, signatureInformation = { activeParameterSupport = true, documentationFormat = { "markdown", "plaintext" }, parameterInformation = { labelOffsetSupport = true } } }, synchronization = { didSave = true, dynamicRegistration = false, willSave = true, willSaveWaitUntil = true }, typeDefinition = { linkSupport = true } }, window = { showDocument = { support = true }, showMessage = { messageActionItem = { additionalPropertiesSupport = false } }, workDoneProgress = true }, workspace = { applyEdit = true, configuration = true, didChangeConfiguration = { dynamicRegistration = false }, didChangeWatchedFiles = { dynamicRegistration = false, relativePatternSupport = true }, inlayHint = { refreshSupport = true }, semanticTokens = { refreshSupport = true }, symbol = { dynamicRegistration = false, symbolKind = { valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 } } }, workspaceEdit = { resourceOperations = { "rename", "create", "delete" } }, workspaceFolders = true } }, clientInfo = { name = "Neovim", version = "0.10.3+g9b5ee7df4e" }, initializationOptions = vim.empty_dict(), processId = 3698395, rootPath = vim.NIL, rootUri = vim.NIL, trace = "off", workDoneToken = "1", workspaceFolders = vim.NIL }}
[ERROR][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:770 "rpc" "/home/netvor/.local/share/nvim/mason/bin/zls" "stderr""info : ( main ): Starting ZLS 0.13.0 @ '/home/netvor/.local/share/nvim/mason/bin/zls'\ninfo : (server): Client is 'Neovim-0.10.3+g9b5ee7df4e'\ninfo : (server): No config file zls.json found. This is not an error.\n"
[ERROR][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:770 "rpc" "/home/netvor/.local/share/nvim/mason/bin/zls" "stderr""info : (server): Set config option 'builtin_path' to '/home/netvor/.cache/zls/builtin.zig'\ninfo : (server): Set config option 'zig_lib_path' to '/opt/zigdev/zig-linux-x86_64/lib'\ninfo : (server): Set config option 'zig_exe_path' to '/usr/bin/zig'\ninfo : (server): Set config option 'build_runner_path' to '/home/netvor/.cache/zls/build_runner/21872970afd69e48a0847077e5196711/build_runner.zig'\ninfo : (server): Set config option 'global_cache_path' to '/home/netvor/.cache/zls'\n"
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:408 "rpc.receive" { id = 1, jsonrpc = "2.0", result = { capabilities = { codeActionProvider = true, colorProvider = false, completionProvider = { completionItem = { labelDetailsSupport = true }, resolveProvider = false, triggerCharacters = { ".", ":", "@", "]", "/" } }, declarationProvider = true, definitionProvider = true, documentFormattingProvider = true, documentHighlightProvider = true, documentRangeFormattingProvider = false, documentSymbolProvider = true, foldingRangeProvider = true, hoverProvider = true, implementationProvider = false, inlayHintProvider = true, positionEncoding = "utf-16", referencesProvider = true, renameProvider = true, selectionRangeProvider = true, semanticTokensProvider = { full = true, legend = { tokenModifiers = { "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary", "generic", "_" }, tokenTypes = { "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator", "decorator", "errorTag", "builtin", "label", "keywordLiteral", "union", "opaque" } }, range = true }, signatureHelpProvider = { retriggerCharacters = { "," }, triggerCharacters = { "(" } }, textDocumentSync = { change = 2, openClose = true, save = true, willSave = true, willSaveWaitUntil = true }, typeDefinitionProvider = true, workspace = { workspaceFolders = { changeNotifications = true, supported = true } }, workspaceSymbolProvider = false }, serverInfo = { name = "zls", version = "0.13.0" } }}
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:286 "rpc.send" { jsonrpc = "2.0", method = "initialized", params = vim.empty_dict()}
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:286 "rpc.send" { jsonrpc = "2.0", method = "textDocument/didOpen", params = { textDocument = { languageId = "zig", text = "const OPTIONAL_STR = ?[]const u8;\nconst OPTIONAL_USIZE = ?usize;\nconst AN_USIZE = usize;\n\nconst FOO = OPTIONAL_USIZE orelse @max(1, AN_USIZE);\n\n// ,-- Gets stuck here.\n// vvv\nconst BAR = if (OPTIONAL_STR == null) {\n (OPTIONAL_USIZE orelse (FOO + 1) / 2);\n} else null;\n", uri = "file:///home/netvor/killzls.zig", version = 0 } }}
[INFO][2025-02-07 15:43:38] ...m/lsp/client.lua:620 "LSP[zls]" "server_capabilities" { server_capabilities = { codeActionProvider = true, colorProvider = false, completionProvider = { completionItem = { labelDetailsSupport = true }, resolveProvider = false, triggerCharacters = { ".", ":", "@", "]", "/" } }, declarationProvider = true, definitionProvider = true, documentFormattingProvider = true, documentHighlightProvider = true, documentRangeFormattingProvider = false, documentSymbolProvider = true, foldingRangeProvider = true, hoverProvider = true, implementationProvider = false, inlayHintProvider = true, positionEncoding = "utf-16", referencesProvider = true, renameProvider = true, selectionRangeProvider = true, semanticTokensProvider = { full = true, legend = { tokenModifiers = { "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary", "generic", "_" }, tokenTypes = { "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator", "decorator", "errorTag", "builtin", "label", "keywordLiteral", "union", "opaque" } }, range = true }, signatureHelpProvider = { retriggerCharacters = { "," }, triggerCharacters = { "(" } }, textDocumentSync = { change = 2, openClose = true, save = true, willSave = true, willSaveWaitUntil = true }, typeDefinitionProvider = true, workspace = { workspaceFolders = { changeNotifications = true, supported = true } }, workspaceSymbolProvider = false }}
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:408 "rpc.receive" { id = "i_haz_configuration", jsonrpc = "2.0", method = "workspace/configuration", params = { items = { { section = "zls.enable_snippets" }, { section = "zls.enable_argument_placeholders" }, { section = "zls.enable_build_on_save" }, { section = "zls.build_on_save_step" }, { section = "zls.enable_autofix" }, { section = "zls.semantic_tokens" }, { section = "zls.enable_inlay_hints" }, { section = "zls.inlay_hints_show_variable_type_hints" }, { section = "zls.inlay_hints_show_struct_literal_field_type" }, { section = "zls.inlay_hints_show_parameter_name" }, { section = "zls.inlay_hints_show_builtin" }, { section = "zls.inlay_hints_exclude_single_argument" }, { section = "zls.inlay_hints_hide_redundant_param_names" }, { section = "zls.inlay_hints_hide_redundant_param_names_last_token" }, { section = "zls.warn_style" }, { section = "zls.highlight_global_var_declarations" }, { section = "zls.dangerous_comptime_experiments_do_not_enable" }, { section = "zls.skip_std_references" }, { section = "zls.prefer_ast_check_as_child_process" }, { section = "zls.builtin_path" }, { section = "zls.zig_lib_path" }, { section = "zls.zig_exe_path" }, { section = "zls.build_runner_path" }, { section = "zls.global_cache_path" }, { section = "zls.completion_label_details" } } }}
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:408 "rpc.receive" { jsonrpc = "2.0", method = "textDocument/publishDiagnostics", params = { diagnostics = {}, uri = "file:///home/netvor/killzls.zig" }}
[DEBUG][2025-02-07 15:43:38] ...m/lsp/client.lua:678 "LSP[zls]" "client.request" 1 "textDocument/semanticTokens/full" { textDocument = { uri = "file:///home/netvor/killzls.zig" }} <function 1> 1
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:286 "rpc.send" { id = 2, jsonrpc = "2.0", method = "textDocument/semanticTokens/full", params = { textDocument = { uri = "file:///home/netvor/killzls.zig" } }}
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:423 "server_request: callback result" { result = { vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL }, status = true}
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:286 "rpc.send" { id = "i_haz_configuration", jsonrpc = "2.0", result = { vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL, vim.NIL }}
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:408 "rpc.receive" { id = 2, jsonrpc = "2.0", result = { data = { 0, 0, 5, 15, 0, 0, 6, 12, 1, 1, 0, 13, 1, 21, 0, 0, 2, 1, 21, 0, 0, 3, 5, 15, 0, 0, 6, 2, 1, 0, 1, 0, 5, 15, 0, 0, 6, 14, 1, 1, 0, 15, 1, 21, 0, 0, 2, 1, 21, 0, 0, 1, 5, 1, 0, 1, 0, 5, 15, 0, 0, 6, 8, 1, 1, 0, 9, 1, 21, 0, 0, 2, 5, 1, 0, 2, 0, 5, 15, 0, 0, 6, 3, 8, 1, 0, 4, 1, 21, 0, 0, 2, 14, 1, 0, 0, 15, 6, 15, 0, 0, 7, 4, 24, 0, 0, 5, 1, 19, 0, 0, 3, 8, 1, 0, 2, 0, 29, 17, 0, 1, 0, 9, 17, 0, 1, 0, 5, 15, 0, 0, 6, 3, 8, 1, 0, 4, 1, 21, 0, 0, 2, 2, 15, 0, 0, 4, 12, 1, 0, 0, 13, 2, 21, 0, 0, 3, 4, 26, 0, 1, 5, 14, 1, 0, 0, 15, 6, 15, 0, 0, 8, 3, 8, 0, 0, 4, 1, 21, 0, 0, 2, 1, 19, 0, 0, 3, 1, 21, 0, 0, 2, 1, 19, 0, 1, 2, 4, 15, 0, 0, 5, 4, 26, 0 } }}
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:408 "rpc.receive" { id = "semantic_tokens_refresh", jsonrpc = "2.0", method = "workspace/semanticTokens/refresh"}
[DEBUG][2025-02-07 15:43:38] ...m/lsp/client.lua:678 "LSP[zls]" "client.request" 1 "textDocument/semanticTokens/full" { textDocument = { uri = "file:///home/netvor/killzls.zig" }} <function 1> 1
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:286 "rpc.send" { id = 3, jsonrpc = "2.0", method = "textDocument/semanticTokens/full", params = { textDocument = { uri = "file:///home/netvor/killzls.zig" } }}
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:423 "server_request: callback result" { result = vim.NIL, status = true}
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:286 "rpc.send" { id = "semantic_tokens_refresh", jsonrpc = "2.0", result = vim.NIL}
[DEBUG][2025-02-07 15:43:38] .../vim/lsp/rpc.lua:408 "rpc.receive" { id = 3, jsonrpc = "2.0", result = { data = { 0, 0, 5, 15, 0, 0, 6, 12, 1, 1, 0, 13, 1, 21, 0, 0, 2, 1, 21, 0, 0, 3, 5, 15, 0, 0, 6, 2, 1, 0, 1, 0, 5, 15, 0, 0, 6, 14, 1, 1, 0, 15, 1, 21, 0, 0, 2, 1, 21, 0, 0, 1, 5, 1, 0, 1, 0, 5, 15, 0, 0, 6, 8, 1, 1, 0, 9, 1, 21, 0, 0, 2, 5, 1, 0, 2, 0, 5, 15, 0, 0, 6, 3, 8, 1, 0, 4, 1, 21, 0, 0, 2, 14, 1, 0, 0, 15, 6, 15, 0, 0, 7, 4, 24, 0, 0, 5, 1, 19, 0, 0, 3, 8, 1, 0, 2, 0, 29, 17, 0, 1, 0, 9, 17, 0, 1, 0, 5, 15, 0, 0, 6, 3, 8, 1, 0, 4, 1, 21, 0, 0, 2, 2, 15, 0, 0, 4, 12, 1, 0, 0, 13, 2, 21, 0, 0, 3, 4, 26, 0, 1, 5, 14, 1, 0, 0, 15, 6, 15, 0, 0, 8, 3, 8, 0, 0, 4, 1, 21, 0, 0, 2, 1, 19, 0, 0, 3, 1, 21, 0, 0, 2, 1, 19, 0, 1, 2, 4, 15, 0, 0, 5, 4, 26, 0 } }}
[DEBUG][2025-02-07 15:43:41] ...m/lsp/client.lua:678 "LSP[zls]" "client.request" 1 "textDocument/hover" { position = { character = 6, line = 8 }, textDocument = { uri = "file:///home/netvor/killzls.zig" }} <function 1> 1
[DEBUG][2025-02-07 15:43:41] .../vim/lsp/rpc.lua:286 "rpc.send" { id = 4, jsonrpc = "2.0", method = "textDocument/hover", params = { position = { character = 6, line = 8 }, textDocument = { uri = "file:///home/netvor/killzls.zig" } }}
[DEBUG][2025-02-07 15:43:41] .../vim/lsp/rpc.lua:408 "rpc.receive" { id = 4, jsonrpc = "2.0", result = { contents = { kind = "markdown", value = "\nconst BAR = if (OPTIONAL_STR == null) {\n (OPTIONAL_USIZE orelse (FOO + 1) / 2);\n} else null\n\n\n(@TypeOf(null))\n" }, range = { ["end"] = { character = 9, line = 8 }, start = { character = 6, line = 8 } } }}
I have similar issue, but not for hover doc. I'm using blink.cmp for completions and it randomly gets stuck, using 100% cpu. Weirdly, this only happens for zls and it consistently fails on one specific code snippet
fn parse(filename: []const u8) !i32 {
var fd = try std.fs.cwd().openFile(filename, .{});
defer fd.cl
// ^
// cursor is here
}
as soon as close method becomes best match, nvim crashes.
I tried your provided snipped but that worked fine for me, so I think this might be blink.cmp issue,
but if so, then why does it only happen to zls
related #2139
Is this issue still happening? If so, please provide more information on how the editor has been configured (plugins, config files, etc.). I had previously tested this with relatively bare bones neovim config and could not reproduce this issue.
I just tried on the same machine with nvim-0.11.0 and zls-0.14.0 and it works fine. (It even gets the type right.)
iirc, this was likely an issue in treesitter. Then after a month or so I tried to track the bug down again and it didn't repro anymore
So I guess there is nothing to do here. Closing.