'__close' gets no error object if there is no error

Instead of receiving nil as a second argument, __close metamethods are
called with just one argument when there are no errors.
This commit is contained in:
Roberto Ierusalimschy 2025-02-28 10:10:27 -03:00
parent f9e35627ed
commit 127a8e80fe
4 changed files with 60 additions and 27 deletions

4
ldo.c
View File

@ -111,10 +111,6 @@ void luaD_seterrorobj (lua_State *L, TStatus errcode, StkId oldtop) {
setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling"));
break;
}
case LUA_OK: { /* special case only for closing upvalues */
setnilvalue(s2v(oldtop)); /* no error message */
break;
}
default: {
lua_assert(errorstatus(errcode)); /* real error */
setobjs2s(L, oldtop, L->top.p - 1); /* error message on current top */

32
lfunc.c
View File

@ -100,21 +100,23 @@ UpVal *luaF_findupval (lua_State *L, StkId level) {
/*
** Call closing method for object 'obj' with error message 'err'. The
** Call closing method for object 'obj' with error object 'err'. The
** boolean 'yy' controls whether the call is yieldable.
** (This function assumes EXTRA_STACK.)
*/
static void callclosemethod (lua_State *L, TValue *obj, TValue *err, int yy) {
StkId top = L->top.p;
StkId func = top;
const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE);
setobj2s(L, top, tm); /* will call metamethod... */
setobj2s(L, top + 1, obj); /* with 'self' as the 1st argument */
setobj2s(L, top + 2, err); /* and error msg. as 2nd argument */
L->top.p = top + 3; /* add function and arguments */
setobj2s(L, top++, tm); /* will call metamethod... */
setobj2s(L, top++, obj); /* with 'self' as the 1st argument */
if (err != NULL) /* if there was an error... */
setobj2s(L, top++, err); /* then error object will be 2nd argument */
L->top.p = top; /* add function and arguments */
if (yy)
luaD_call(L, top, 0);
luaD_call(L, func, 0);
else
luaD_callnoyield(L, top, 0);
luaD_callnoyield(L, func, 0);
}
@ -144,11 +146,17 @@ static void prepcallclosemth (lua_State *L, StkId level, TStatus status,
int yy) {
TValue *uv = s2v(level); /* value being closed */
TValue *errobj;
if (status == CLOSEKTOP)
errobj = &G(L)->nilvalue; /* error object is nil */
else { /* 'luaD_seterrorobj' will set top to level + 2 */
errobj = s2v(level + 1); /* error object goes after 'uv' */
luaD_seterrorobj(L, status, level + 1); /* set error object */
switch (status) {
case LUA_OK:
L->top.p = level + 1; /* call will be at this level */
/* FALLTHROUGH */
case CLOSEKTOP: /* don't need to change top */
errobj = NULL; /* no error object */
break;
default: /* 'luaD_seterrorobj' will set top to level + 2 */
errobj = s2v(level + 1); /* error object goes after 'uv' */
luaD_seterrorobj(L, status, level + 1); /* set error object */
break;
}
callclosemethod(L, uv, errobj, yy);
}

View File

@ -1612,10 +1612,11 @@ or exiting by an error.
Here, to @emph{close} a value means
to call its @idx{__close} metamethod.
When calling the metamethod,
the value itself is passed as the first argument
and the error object that caused the exit (if any)
the value itself is passed as the first argument.
If there was an error,
the error object that caused the exit
is passed as a second argument;
if there was no error, the second argument is @nil.
otherwise, there is no second argument.
The value assigned to a to-be-closed variable
must have a @idx{__close} metamethod

View File

@ -280,6 +280,32 @@ do
end
do -- testing presence of second argument
local function foo (howtoclose, obj, n)
local ca -- copy of 'a' visible inside its close metamethod
do
local a <close> = func2close(function (...)
local t = table.pack(...)
assert(select("#", ...) == n)
assert(t.n == n and t[1] == ca and (t.n < 2 or t[2] == obj))
ca = 15 -- final value to be returned if howtoclose=="scope"
end)
ca = a
if howtoclose == "ret" then return obj -- 'a' closed by return
elseif howtoclose == "err" then error(obj) -- 'a' closed by error
end
end -- 'a' closed by end of scope
return ca -- ca now should be 15
end
-- with no errors, closing methods receive no extra argument
assert(foo("scope", nil, 1) == 15) -- close by end of scope
assert(foo("ret", 32, 1) == 32) -- close by return
-- with errors, they do
local st, msg = pcall(foo, "err", 23, 2) -- close by error
assert(not st and msg == 23)
end
-- testing to-be-closed x compile-time constants
-- (there were some bugs here in Lua 5.4-rc3, due to a confusion
-- between compile levels and stack levels of variables)
@ -865,8 +891,10 @@ do
if extra then
extrares = co() -- runs until first (extra) yield
end
local res = table.pack(co()) -- runs until yield inside '__close'
assert(res.n == 2 and res[2] == nil)
local res = table.pack(co()) -- runs until "regular" yield
-- regular yield will yield all values passed to the close function;
-- without errors, that is only the object being closed.
assert(res.n == 1 and type(res[1]) == "table")
local res2 = table.pack(co()) -- runs until end of function
assert(res2.n == t.n)
for i = 1, #t do
@ -879,10 +907,10 @@ do
end
local function foo ()
local x <close> = func2close(coroutine.yield)
local x <close> = func2close(coroutine.yield) -- "regular" yield
local extra <close> = func2close(function (self)
assert(self == extrares)
coroutine.yield(100)
coroutine.yield(100) -- first (extra) yield
end)
extrares = extra
return table.unpack{10, x, 30}
@ -891,21 +919,21 @@ do
assert(extrares == 100)
local function foo ()
local x <close> = func2close(coroutine.yield)
local x <close> = func2close(coroutine.yield) -- "regular" yield
return
end
check(foo, false)
local function foo ()
local x <close> = func2close(coroutine.yield)
local x <close> = func2close(coroutine.yield) -- "regular" yield
local y, z = 20, 30
return x
end
check(foo, false, "x")
local function foo ()
local x <close> = func2close(coroutine.yield)
local extra <close> = func2close(coroutine.yield)
local x <close> = func2close(coroutine.yield) -- "regular" yield
local extra <close> = func2close(coroutine.yield) -- extra yield
return table.unpack({}, 1, 100) -- 100 nils
end
check(foo, true, table.unpack({}, 1, 100))