fix(lua): allows tables with integer keys to be merged in tbl_deep_extend

- The exclusion of lists was never justified in the commit history and is
  the wrong thing to do for a function that deals with tables.

- Move the error checks out of the recursive path.

Fixes #23654
This commit is contained in:
Lewis Russell
2024-09-03 17:14:25 +01:00
committed by Lewis Russell
parent 7b7c95dac9
commit b6e350a6b4
3 changed files with 42 additions and 24 deletions

View File

@ -210,6 +210,9 @@ These existing features changed their behavior.
more emoji characters than before, including those encoded with multiple more emoji characters than before, including those encoded with multiple
emoji codepoints combined with ZWJ (zero width joiner) codepoints. emoji codepoints combined with ZWJ (zero width joiner) codepoints.
• |vim.tbl_deep_extend()| no longer ignores any values for which |vim.isarray()|
returns `true`.
============================================================================== ==============================================================================
REMOVED FEATURES *news-removed* REMOVED FEATURES *news-removed*

View File

@ -354,37 +354,22 @@ function vim.tbl_isempty(t)
return next(t) == nil return next(t) == nil
end end
--- We only merge empty tables or tables that are not an array (indexed by integers) --- Recursive worker for tbl_extend
local function can_merge(v) --- @param behavior 'error'|'keep'|'force'
return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.isarray(v)) --- @param deep_extend boolean
end --- @param ... table<any,any>
local function tbl_extend_rec(behavior, deep_extend, ...)
local function tbl_extend(behavior, deep_extend, ...)
if behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force' then
error('invalid "behavior": ' .. tostring(behavior))
end
if select('#', ...) < 2 then
error(
'wrong number of arguments (given '
.. tostring(1 + select('#', ...))
.. ', expected at least 3)'
)
end
local ret = {} --- @type table<any,any> local ret = {} --- @type table<any,any>
if vim._empty_dict_mt ~= nil and getmetatable(select(1, ...)) == vim._empty_dict_mt then if vim._empty_dict_mt ~= nil and getmetatable(select(1, ...)) == vim._empty_dict_mt then
ret = vim.empty_dict() ret = vim.empty_dict()
end end
for i = 1, select('#', ...) do for i = 1, select('#', ...) do
local tbl = select(i, ...) local tbl = select(i, ...) --[[@as table<any,any>]]
vim.validate('after the second argument', tbl, 'table')
--- @cast tbl table<any,any>
if tbl then if tbl then
for k, v in pairs(tbl) do for k, v in pairs(tbl) do
if deep_extend and can_merge(v) and can_merge(ret[k]) then if deep_extend and type(v) == 'table' and type(ret[k]) == 'table' then
ret[k] = tbl_extend(behavior, true, ret[k], v) ret[k] = tbl_extend_rec(behavior, true, ret[k], v)
elseif behavior ~= 'force' and ret[k] ~= nil then elseif behavior ~= 'force' and ret[k] ~= nil then
if behavior == 'error' then if behavior == 'error' then
error('key found in more than one map: ' .. k) error('key found in more than one map: ' .. k)
@ -395,9 +380,31 @@ local function tbl_extend(behavior, deep_extend, ...)
end end
end end
end end
return ret return ret
end end
--- @param behavior 'error'|'keep'|'force'
--- @param deep_extend boolean
--- @param ... table<any,any>
local function tbl_extend(behavior, deep_extend, ...)
if behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force' then
error('invalid "behavior": ' .. tostring(behavior))
end
local nargs = select('#', ...)
if nargs < 2 then
error(('wrong number of arguments (given %d, expected at least 3)'):format(1 + nargs))
end
for i = 1, nargs do
vim.validate('after the second argument', select(i, ...), 'table')
end
return tbl_extend_rec(behavior, deep_extend, ...)
end
--- Merges two or more tables. --- Merges two or more tables.
--- ---
---@see |extend()| ---@see |extend()|

View File

@ -1059,7 +1059,7 @@ describe('lua stdlib', function()
local a = { a = {[2] = 3} } local a = { a = {[2] = 3} }
local b = { a = {[3] = 3} } local b = { a = {[3] = 3} }
local c = vim.tbl_deep_extend("force", a, b) local c = vim.tbl_deep_extend("force", a, b)
return vim.deep_equal(c, {a = {[3] = 3}}) return vim.deep_equal(c, {a = {[2] = 3, [3] = 3}})
]])) ]]))
eq( eq(
@ -1071,6 +1071,14 @@ describe('lua stdlib', function()
]]) ]])
) )
-- Fix github issue #23654
ok(exec_lua([[
local a = { sub = { [1] = 'a' } }
local b = { sub = { b = 'a' } }
local c = vim.tbl_deep_extend('force', a, b)
return vim.deep_equal(c, { sub = { [1] = 'a', b = 'a' } })
]]))
matches( matches(
'invalid "behavior": nil', 'invalid "behavior": nil',
pcall_err( pcall_err(