fix(diagnostics): don't allow 0 bufnr for metatable index (#16405)

04bfd20bb introduced a subtle bug where using 0 as the buffer number in
the diagnostic cache resets the cache for the current buffer. This
happens because we were not checking to see if the _resolved_ buffer
number already existed in the cache; rather, when the __index metamethod
was called we assumed the index did not exist so we set its value to an
empty table. The fix for this is to check `rawget()` for the resolved
buffer number to see if the index already exists.

However, the reason this bug was introduced in the first place was
because we are simply being too clever by allowing a 0 buffer number as
the index which is automatically resolved to a real buffer number.
In the interest of minimizing metatable magic, remove this "feature" by
requiring the buffer number index to always be a valid buffer. This
ensures that the __index metamethod is only ever called for non-existing
buffers (which is what we wanted originally) as well as reduces some of
the cognitive overhead for understanding how the diagnostic cache works.
The tradeoff is that all public API functions must now resolve 0 buffer
numbers to the current buffer number.
This commit is contained in:
Gregory Anders
2021-11-22 08:47:30 -07:00
committed by GitHub
parent 33ce02ee4d
commit e02d4732f2
2 changed files with 10 additions and 25 deletions

View File

@@ -39,39 +39,22 @@ M.handlers = setmetatable({}, {
-- Metatable that automatically creates an empty table when assigning to a missing key
local bufnr_and_namespace_cacher_mt = {
__index = function(t, bufnr)
if not bufnr or bufnr == 0 then
bufnr = vim.api.nvim_get_current_buf()
end
rawset(t, bufnr, {})
return rawget(t, bufnr)
end,
__newindex = function(t, bufnr, v)
if not bufnr or bufnr == 0 then
bufnr = vim.api.nvim_get_current_buf()
end
rawset(t, bufnr, v)
assert(bufnr > 0, "Invalid buffer number")
t[bufnr] = {}
return t[bufnr]
end,
}
local diagnostic_cache = setmetatable({}, {
__index = function(t, bufnr)
if not bufnr or bufnr == 0 then
bufnr = vim.api.nvim_get_current_buf()
end
assert(bufnr > 0, "Invalid buffer number")
vim.api.nvim_buf_attach(bufnr, false, {
on_detach = function()
rawset(t, bufnr, nil) -- clear cache
end
})
rawset(t, bufnr, {})
return rawget(t, bufnr)
t[bufnr] = {}
return t[bufnr]
end,
})
@@ -659,6 +642,8 @@ function M.set(namespace, bufnr, diagnostics, opts)
opts = {opts, 't', true},
}
bufnr = get_bufnr(bufnr)
if vim.tbl_isempty(diagnostics) then
diagnostic_cache[bufnr][namespace] = nil
else
@@ -1320,7 +1305,7 @@ function M.reset(namespace, bufnr)
bufnr = {bufnr, 'n', true},
}
local buffers = bufnr and {bufnr} or vim.tbl_keys(diagnostic_cache)
local buffers = bufnr and {get_bufnr(bufnr)} or vim.tbl_keys(diagnostic_cache)
for _, iter_bufnr in ipairs(buffers) do
local namespaces = namespace and {namespace} or vim.tbl_keys(diagnostic_cache[iter_bufnr])
for _, iter_namespace in ipairs(namespaces) do