fix(lua): fix vim.deepcopy for metatables & cycled tables (#16435)

vim.deepcopy previously didn't retain metatables in copies
and caused stackoverflow on recursive tables/cycled tables this
fixes these issues
This commit is contained in:
Shadman 2021-11-26 16:06:43 +06:00 committed by GitHub
parent 3451121a4e
commit eb876a0a6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 23 additions and 10 deletions

View File

@ -12,7 +12,7 @@ local vim = vim or {}
--- same functions as those in the input table. Userdata and threads are not --- same functions as those in the input table. Userdata and threads are not
--- copied and will throw an error. --- copied and will throw an error.
--- ---
---@param orig Table to copy ---@param orig table Table to copy
---@returns New table of copied keys and (nested) values. ---@returns New table of copied keys and (nested) values.
function vim.deepcopy(orig) end -- luacheck: no unused function vim.deepcopy(orig) end -- luacheck: no unused
vim.deepcopy = (function() vim.deepcopy = (function()
@ -21,17 +21,16 @@ vim.deepcopy = (function()
end end
local deepcopy_funcs = { local deepcopy_funcs = {
table = function(orig) table = function(orig, cache)
if cache[orig] then return cache[orig] end
local copy = {} local copy = {}
if vim._empty_dict_mt ~= nil and getmetatable(orig) == vim._empty_dict_mt then cache[orig] = copy
copy = vim.empty_dict() local mt = getmetatable(orig)
end
for k, v in pairs(orig) do for k, v in pairs(orig) do
copy[vim.deepcopy(k)] = vim.deepcopy(v) copy[vim.deepcopy(k, cache)] = vim.deepcopy(v, cache)
end end
return copy return setmetatable(copy, mt)
end, end,
number = _id, number = _id,
string = _id, string = _id,
@ -40,10 +39,10 @@ vim.deepcopy = (function()
['function'] = _id, ['function'] = _id,
} }
return function(orig) return function(orig, cache)
local f = deepcopy_funcs[type(orig)] local f = deepcopy_funcs[type(orig)]
if f then if f then
return f(orig) return f(orig, cache or {})
else else
error("Cannot deepcopy object of type "..type(orig)) error("Cannot deepcopy object of type "..type(orig))
end end

View File

@ -396,6 +396,20 @@ describe('lua stdlib', function()
return t1.f() ~= t2.f() return t1.f() ~= t2.f()
]])) ]]))
ok(exec_lua([[
local t1 = {a = 5}
t1.self = t1
local t2 = vim.deepcopy(t1)
return t2.self == t2 and t2.self ~= t1
]]))
ok(exec_lua([[
local mt = {mt=true}
local t1 = setmetatable({a = 5}, mt)
local t2 = vim.deepcopy(t1)
return getmetatable(t2) == mt
]]))
eq('Error executing lua: vim/shared.lua:0: Cannot deepcopy object of type thread', eq('Error executing lua: vim/shared.lua:0: Cannot deepcopy object of type thread',
pcall_err(exec_lua, [[ pcall_err(exec_lua, [[
local thread = coroutine.create(function () return 0 end) local thread = coroutine.create(function () return 0 end)