From 3a881132460430d23f2fdc87822c87d47f721cfc Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 8 Sep 2024 18:23:46 +0200 Subject: [PATCH] fix(lua): revert vim.tbl_extend behavior change and document it Problem: vim.tbl_deep_extend had an undocumented feature where arrays (integer-indexed tables) were not merged but compared literally (used for merging default and user config, where one list should overwrite the other completely). Turns out this behavior was relied on in quite a number of plugins (even though it wasn't a robust solution even for that use case, since lists of tables (e.g., plugin specs) can be array-like as well). Solution: Revert the removal of this special feature. Check for list-like (contiguous integer indices) instead, as this is closer to the intent. Document this behavior. --- runtime/doc/lua.txt | 6 ++++++ runtime/doc/news.txt | 3 --- runtime/lua/vim/shared.lua | 13 ++++++++++++- test/functional/lua/vim_spec.lua | 7 +++---- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 0a7c53a482..0beee1507a 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2230,6 +2230,12 @@ vim.tbl_count({t}) *vim.tbl_count()* vim.tbl_deep_extend({behavior}, {...}) *vim.tbl_deep_extend()* Merges recursively two or more tables. + Only values that are empty tables or tables that are not |lua-list|s + (indexed by consecutive integers starting from 1) are merged recursively. + This is useful for merging nested tables like default and user + configurations where lists should be treated as literals (i.e., are + overwritten instead of merged). + Parameters: ~ • {behavior} (`'error'|'keep'|'force'`) Decides what to do if a key is found in more than one map: diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index ff41238912..bc1b9487b7 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -214,9 +214,6 @@ These existing features changed their behavior. more emoji characters than before, including those encoded with multiple 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* diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 2f10380bad..4d06cdd77d 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -354,6 +354,12 @@ function vim.tbl_isempty(t) return next(t) == nil end +--- We only merge empty tables or tables that are not list-like (indexed by consecutive integers +--- starting from 1) +local function can_merge(v) + return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.islist(v)) +end + --- Recursive worker for tbl_extend --- @param behavior 'error'|'keep'|'force' --- @param deep_extend boolean @@ -368,7 +374,7 @@ local function tbl_extend_rec(behavior, deep_extend, ...) local tbl = select(i, ...) --[[@as table]] if tbl then for k, v in pairs(tbl) do - if deep_extend and type(v) == 'table' and type(ret[k]) == 'table' then + if deep_extend and can_merge(v) and can_merge(ret[k]) then ret[k] = tbl_extend_rec(behavior, true, ret[k], v) elseif behavior ~= 'force' and ret[k] ~= nil then if behavior == 'error' then @@ -421,6 +427,11 @@ end --- Merges recursively two or more tables. --- +--- Only values that are empty tables or tables that are not |lua-list|s (indexed by consecutive +--- integers starting from 1) are merged recursively. This is useful for merging nested tables +--- like default and user configurations where lists should be treated as literals (i.e., are +--- overwritten instead of merged). +--- ---@see |vim.tbl_extend()| --- ---@generic T1: table diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 7f10dcd8da..599b688bf4 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -1071,12 +1071,11 @@ describe('lua stdlib', function() ]]) ) - -- Fix github issue #23654 ok(exec_lua([[ - local a = { sub = { [1] = 'a' } } - local b = { sub = { b = 'a' } } + local a = { sub = { 'a', 'b' } } + local b = { sub = { 'b', 'c' } } local c = vim.tbl_deep_extend('force', a, b) - return vim.deep_equal(c, { sub = { [1] = 'a', b = 'a' } }) + return vim.deep_equal(c, { sub = { 'b', 'c' } }) ]])) matches('invalid "behavior": nil', pcall_err(exec_lua, [[return vim.tbl_deep_extend()]]))