From 378f5f49b3c2168959ff6d03e287306f67fae06c Mon Sep 17 00:00:00 2001 From: "neovim-backports[bot]" <175700243+neovim-backports[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:27:50 -0400 Subject: [PATCH] backport: fix(lsp): show meaningful error on invalid completion response (#39476) Problem: vim.NIL is truthy in Lua, so `#(result.items or result)` crashes on `#vim.NIL` when servers return null. Solution: skip spec-allowed result=null silently, raise an error on items=null with the server name. https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion (cherry picked from commit b9431b340f6fd4e886c18d0b117883d6e425ec81) Co-authored-by: glepnir --- runtime/lua/vim/lsp/completion.lua | 8 +++++++- test/functional/plugin/lsp/completion_spec.lua | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index b9c5b52a00..d4fa3dbe49 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -1003,7 +1003,13 @@ local function trigger(bufnr, clients, ctx) end local result = response.result - if result and #(result.items or result) > 0 then + if type(result) == 'table' and result.items == vim.NIL then + error( + ('%s: completion response has items=null, expected CompletionItem[]'):format( + client and client.name or 'UNKNOWN' + ) + ) + elseif result and result ~= vim.NIL and #(result.items or result) > 0 then Context.isIncomplete = Context.isIncomplete or result.isIncomplete local encoding = client and client.offset_encoding or 'utf-16' local client_matches, tmp_server_start_boundary diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 1f6b0ba1d9..f6e8cf2f48 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -1182,6 +1182,24 @@ describe('vim.lsp.completion: protocol', function() eq({ triggerKind = 2, triggerCharacter = 'h' }, exec_lua('return _G.params.context')) end) end) + + it('errors on invalid items=null in completion response #39400', function() + create_server('dummy', { + isIncomplete = false, + items = vim.NIL, + }) + feed('ih') + local err = t.pcall_err(function() + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + vim.lsp.completion.get() + vim.wait(1000, function() + return false + end) + end) + end) + t.matches('items=null', err) + end) end) describe('vim.lsp.completion: integration', function()