refactor(lsp): remove implicit rpc error tostring #38707

Problem:
LSP error responses implicitly rely on a custom `__tostring` function
(`vim.lsp.rpc.format_rpc_error`) for formatting. This causes errors that are not
created via `vim.lsp.rpc.error` to behave inconsistently with those that are.

Furthermore, we usually use `log.error` to print these errors, which uses
`vim.inspect` under the hood, so the custom `__tostring`  provides little
benefit.

This increases the difficulty of refactoring the code, as it tightly couples RPC
error handling with the LSP.

Solution:
Convert every potential `__tostring` call to an explicit one. Since we don't
describe this behavior in the documentation, this should not be a breaking
change.
This commit is contained in:
Yi Ming
2026-04-02 20:53:29 +08:00
committed by GitHub
parent 2cf5dcb170
commit 0eb2eb4106
5 changed files with 15 additions and 11 deletions

View File

@@ -2264,7 +2264,10 @@ get({filter}) *vim.lsp.inlay_hint.get()*
local client = vim.lsp.get_client_by_id(hint.client_id)
local resp = client:request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0)
local resolved_hint = assert(resp and resp.result, resp.err)
local resolved_hint = assert(
resp and resp.result,
resp and resp.err and vim.lsp.rpc.format_rpc_error(resp.err) or 'request failed'
)
vim.lsp.util.apply_text_edits(resolved_hint.textEdits, 0, client.encoding)
location = resolved_hint.label[1].location

View File

@@ -1460,7 +1460,8 @@ function M.selection_range(direction, timeout_ms)
timeout_ms = timeout_ms or 1000
local result, err = lsp.buf_request_sync(0, method, params, timeout_ms)
if err then
lsp.log.error('selectionRange request failed: ' .. err)
local err_message = type(err) == 'table' and lsp.rpc.format_rpc_error(err) or err
lsp.log.error('selectionRange request failed: ' .. err_message)
return
end
if not result or not result[client.id] or not result[client.id].result then

View File

@@ -578,7 +578,9 @@ function Client:initialize()
local rpc = self.rpc
rpc.request('initialize', init_params, function(init_err, result)
assert(not init_err, tostring(init_err))
if init_err then
error(lsp.rpc.format_rpc_error(init_err))
end
assert(result, 'server sent empty result')
rpc.notify('initialized', vim.empty_dict())
self.initialized = true

View File

@@ -152,7 +152,10 @@ end
---
--- local client = vim.lsp.get_client_by_id(hint.client_id)
--- local resp = client:request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0)
--- local resolved_hint = assert(resp and resp.result, resp.err)
--- local resolved_hint = assert(
--- resp and resp.result,
--- resp and resp.err and vim.lsp.rpc.format_rpc_error(resp.err) or 'request failed'
--- )
--- vim.lsp.util.apply_text_edits(resolved_hint.textEdits, 0, client.encoding)
---
--- location = resolved_hint.label[1].location

View File

@@ -137,13 +137,11 @@ function M.rpc_response_error(code, message, data)
-- TODO should this error or just pick a sane error (like InternalError)?
---@type string
local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code')
return setmetatable({
return {
code = code,
message = message or code_name,
data = data,
}, {
__tostring = M.format_rpc_error,
})
}
end
--- Dispatchers for LSP message types.
@@ -540,9 +538,6 @@ function Client:handle_body(body)
if callback then
self.message_callbacks[result_id] = nil
validate('callback', callback, 'function')
if decoded.error then
setmetatable(decoded.error, { __tostring = M.format_rpc_error })
end
self:try_call(
M.client_errors.SERVER_RESULT_CALLBACK_ERROR,
callback,