Files
ClassicAPI/AddOns/!!!ClassicAPI/Util/FunctionUtil.lua
T
Brues aae4eb8a2b fix(util): preserve nil holes in vararg forwarding; drop no-op unpack ranges
On this Lua 5.0 build, unpack() ignores its explicit i/j arguments entirely
and takes its element count from getn() -- which returns the table's `.n`
field, or border-searches to the first nil when `.n` is absent. Verified in
the binary (luaB_unpack 0x007030a0 -> luaL_getn 0x006f5050) and in-game
(unpack({1, 1, nil, 1}, 1, 4) yields just 1, 1).

- GenerateClosure built its forwarding table `rest` with no `.n`, so
  unpack(rest, 1, restN) border-searched and silently dropped every value
  past the first nil hole in the payload -- despite the comment claiming
  embedded nils survive. It now stamps `all.n` and unpack(all) forwards
  holes intact.

- Every other unpack(x, 1, x.n) / unpack(x, 1, count==x.n) call was a no-op:
  the table already carried `.n`, so unpack(x) is identical. Simplified
  across FunctionUtil, Pools, CallbackRegistry, and TableUtil's SafeUnpack,
  and corrected the header comments that recommended the redundant form.
2026-08-04 18:13:14 -05:00

176 lines
5.4 KiB
Lua

-- Backport of FunctionUtil.lua to vanilla 1.12 / Lua 5.0.
--
-- Implementation differences from the modern source:
-- - Lua 5.0 has no select() and no `...` as expression; use the
-- implicit `arg` table (arg.n + arg[i]). unpack(arg) already spans
-- arg[1..arg.n] via getn — this build's unpack ignores explicit i/j
-- args, so the count must live in the table's `.n`.
-- - Lua 5.0 has no `#` operator; use table.getn().
-- - 5.0's xpcall is strictly (f, handler) — no trailing args. We wrap
-- the script call in a local closure that captures frame + arg.
-- - CallErrorHandler isn't in 1.12; we route through geterrorhandler()
-- (same pattern as ItemUtil.lua).
-- - `nop` and `assertsafe` aren't globals in stock 1.12; defined as
-- local fallbacks so this file stays self-contained.
local function nop() end
local function CallErrorHandler(err)
local h = geterrorhandler and geterrorhandler()
if h then return h(err) end
end
-- Match the modern assertsafe semantics: only fires when `condition` is
-- falsy. Calls with just a message string are no-ops (the message ends
-- up as the condition and is truthy) — matches Blizzard's source.
local function assertsafe(condition, msg)
if not condition then
CallErrorHandler(msg or "assertion failed!")
end
end
function ExecuteFrameScript(frame, scriptName, ...)
local script = frame:GetScript(scriptName);
if script then
local args = arg;
local function invoke()
return script(frame, unpack(args));
end
xpcall(invoke, CallErrorHandler);
end
end
function CallMethodOnNearestAncestor(self, methodName, ...)
local ancestor = self:GetParent();
while ancestor and not ancestor[methodName] do
ancestor = ancestor:GetParent();
end
if ancestor then
return true, ancestor[methodName](ancestor, unpack(arg));
end
return false;
end
function GetValueOrCallFunction(tbl, key, ...)
if not tbl then
return;
end
local value = tbl[key];
if type(value) == "function" then
return value(unpack(arg));
else
return value;
end
end
local function CompositeIterator(iteratorCallbackArray)
local count = table.getn(iteratorCallbackArray);
if count == 0 then
return nop;
end
local iteratorIndex = 1;
local currentIterator, currentTable, iteratorKey = iteratorCallbackArray[1]();
local function AdvanceIterators()
if currentIterator == nil then
return nil;
end
local nextKey = currentIterator(currentTable, iteratorKey);
if nextKey ~= nil then
iteratorKey = nextKey;
return nextKey;
end
if iteratorIndex < count then
iteratorIndex = iteratorIndex + 1;
currentIterator, currentTable, iteratorKey = iteratorCallbackArray[iteratorIndex]();
return AdvanceIterators();
end
currentIterator = nil;
return nil;
end
return AdvanceIterators;
end
function IteratePools(...)
local callbackArray = {};
for i = 1, arg.n do
local pool = arg[i];
table.insert(callbackArray, GenerateClosure(pool.EnumerateActive, pool));
end
return CompositeIterator(callbackArray);
end
function IterateTables(iteratorFunction, ...)
local callbackArray = {};
for i = 1, arg.n do
local tbl = arg[i];
table.insert(callbackArray, GenerateClosure(iteratorFunction, tbl));
end
return CompositeIterator(callbackArray);
end
local s_passThroughClosureGenerators = {
function(f) return function(...) return f(unpack(arg)); end; end,
function(f, a) return function(...) return f(a, unpack(arg)); end; end,
function(f, a, b) return function(...) return f(a, b, unpack(arg)); end; end,
function(f, a, b, c) return function(...) return f(a, b, c, unpack(arg)); end; end,
function(f, a, b, c, d) return function(...) return f(a, b, c, d, unpack(arg)); end; end,
function(f, a, b, c, d, e) return function(...) return f(a, b, c, d, e, unpack(arg)); end; end,
};
local s_flatClosureGenerators = {
function(f) return function() return f(); end; end,
function(f, a) return function() return f(a); end; end,
function(f, a, b) return function() return f(a, b); end; end,
function(f, a, b, c) return function() return f(a, b, c); end; end,
function(f, a, b, c, d) return function() return f(a, b, c, d); end; end,
function(f, a, b, c, d, e) return function() return f(a, b, c, d, e); end; end,
};
local function GenerateClosureInternal(generatorArray, f, ...)
local count = arg.n;
local generator = generatorArray[count + 1];
if generator then
return generator(f, unpack(arg));
end
assertsafe(false, "Closure generation does not support more than " .. (table.getn(generatorArray) - 1) .. " parameters");
return nil;
end
-- Syntactic sugar for function(...) return f(a, b, c, ...); end
function GenerateClosure(f, ...)
return GenerateClosureInternal(s_passThroughClosureGenerators, f, unpack(arg));
end
-- Generates a closure with the specified arguments that will ignore extra arguments when called later. Useful for passing
-- through callbacks to APIs where we don't want extra arguments to be passed through, i.e. simple OnClick scripts.
-- This is equivalent to: function() return f(a, b, c); end INSTEAD OF function(...) return f(a, b, c, ...); end
function GenerateFlatClosure(f, ...)
return GenerateClosureInternal(s_flatClosureGenerators, f, unpack(arg));
end
function RunNextFrame(callback)
C_Timer.After(0, callback);
end
FunctionUtil = {};
function FunctionUtil.SafeInvokeMethod(table, methodName, ...)
local method = table[methodName];
if method then
return method(table, unpack(arg));
end
return nil;
end