fix(lua): make vim.deep_equal cycle-safe

AI-assisted: Codex
(cherry picked from commit e289f9579c)
This commit is contained in:
Lewis Russell
2026-03-26 15:25:13 +00:00
committed by Justin M. Keyes
parent 243a5ac5fd
commit 0039785724
3 changed files with 51 additions and 22 deletions

View File

@@ -1630,6 +1630,7 @@ vim.deep_equal({a}, {b}) *vim.deep_equal()*
Tables are compared recursively unless they both provide the `eq`
metamethod. All other types are compared using the equality `==` operator.
Cyclic tables are supported.
Parameters: ~
• {a} (`any`) First value

View File

@@ -661,36 +661,61 @@ function vim.tbl_deep_extend(behavior, ...)
return tbl_extend(behavior, true, ...)
end
---@param left any
---@param right any
---@param seen? table<table, table<table, boolean>>
---@return boolean
local function deep_equal(left, right, seen)
if left == right then
return true
end
if type(left) ~= type(right) then
return false
end
if type(left) ~= 'table' then
return false
end
seen = seen or {}
local seen_left = seen[left]
if seen_left and seen_left[right] ~= nil then
return seen_left[right]
end
seen_left = seen_left or {}
seen[left] = seen_left
-- Assume equality while descending so recursive structures can terminate.
seen_left[right] = true
for k, v in pairs(left) do
if not deep_equal(v, right[k], seen) then
seen_left[right] = false
return false
end
end
for k in pairs(right) do
if left[k] == nil then
seen_left[right] = false
return false
end
end
return true
end
--- Deep compare values for equality
---
--- Tables are compared recursively unless they both provide the `eq` metamethod.
--- All other types are compared using the equality `==` operator.
--- Cyclic tables are supported.
---@param a any First value
---@param b any Second value
---@return boolean `true` if values are equals, else `false`
function vim.deep_equal(a, b)
if a == b then
return true
end
if type(a) ~= type(b) then
return false
end
if type(a) == 'table' then
--- @cast a table<any,any>
--- @cast b table<any,any>
for k, v in pairs(a) do
if not vim.deep_equal(v, b[k]) then
return false
end
end
for k in pairs(b) do
if a[k] == nil then
return false
end
end
return true
end
return false
return deep_equal(a, b)
end
--- Add the reverse lookup values to an existing table.

View File

@@ -1249,10 +1249,13 @@ describe('lua stdlib', function()
eq(true, exec_lua [[ return vim.deep_equal({a={b=1}}, {a={b=1}}) ]])
eq(true, exec_lua [[ return vim.deep_equal({a={b={nil}}}, {a={b={}}}) ]])
eq(true, exec_lua [[ return vim.deep_equal({a=1, [5]=5}, {nil,nil,nil,nil,5,a=1}) ]])
eq(true, exec_lua [[ local shared = {}; return vim.deep_equal({ 1, shared, 1, shared }, { 1, {}, 1, {} }) ]])
eq(true, exec_lua [[ local a,b={},{}; a[1]=a; b[1]=b; return vim.deep_equal(a, b) ]])
eq(false, exec_lua [[ return vim.deep_equal(1, {nil,nil,nil,nil,5,a=1}) ]])
eq(false, exec_lua [[ return vim.deep_equal(1, 3) ]])
eq(false, exec_lua [[ return vim.deep_equal(nil, 3) ]])
eq(false, exec_lua [[ return vim.deep_equal({a=1}, {a=2}) ]])
eq(false, exec_lua [[ local a,b={},{}; a[1]=a; b[1]={}; return vim.deep_equal(a, b) ]])
end)
it('vim.list_extend', function()