'__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")); setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling"));
break; break;
} }
case LUA_OK: { /* special case only for closing upvalues */
setnilvalue(s2v(oldtop)); /* no error message */
break;
}
default: { default: {
lua_assert(errorstatus(errcode)); /* real error */ lua_assert(errorstatus(errcode)); /* real error */
setobjs2s(L, oldtop, L->top.p - 1); /* error message on current top */ 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. ** boolean 'yy' controls whether the call is yieldable.
** (This function assumes EXTRA_STACK.) ** (This function assumes EXTRA_STACK.)
*/ */
static void callclosemethod (lua_State *L, TValue *obj, TValue *err, int yy) { static void callclosemethod (lua_State *L, TValue *obj, TValue *err, int yy) {
StkId top = L->top.p; StkId top = L->top.p;
StkId func = top;
const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE); const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE);
setobj2s(L, top, tm); /* will call metamethod... */ setobj2s(L, top++, tm); /* will call metamethod... */
setobj2s(L, top + 1, obj); /* with 'self' as the 1st argument */ setobj2s(L, top++, obj); /* with 'self' as the 1st argument */
setobj2s(L, top + 2, err); /* and error msg. as 2nd argument */ if (err != NULL) /* if there was an error... */
L->top.p = top + 3; /* add function and arguments */ setobj2s(L, top++, err); /* then error object will be 2nd argument */
L->top.p = top; /* add function and arguments */
if (yy) if (yy)
luaD_call(L, top, 0); luaD_call(L, func, 0);
else 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) { int yy) {
TValue *uv = s2v(level); /* value being closed */ TValue *uv = s2v(level); /* value being closed */
TValue *errobj; TValue *errobj;
if (status == CLOSEKTOP) switch (status) {
errobj = &G(L)->nilvalue; /* error object is nil */ case LUA_OK:
else { /* 'luaD_seterrorobj' will set top to level + 2 */ L->top.p = level + 1; /* call will be at this level */
errobj = s2v(level + 1); /* error object goes after 'uv' */ /* FALLTHROUGH */
luaD_seterrorobj(L, status, level + 1); /* set error object */ 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); callclosemethod(L, uv, errobj, yy);
} }

View File

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

View File

@@ -280,6 +280,32 @@ do
end 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 -- testing to-be-closed x compile-time constants
-- (there were some bugs here in Lua 5.4-rc3, due to a confusion -- (there were some bugs here in Lua 5.4-rc3, due to a confusion
-- between compile levels and stack levels of variables) -- between compile levels and stack levels of variables)
@@ -865,8 +891,10 @@ do
if extra then if extra then
extrares = co() -- runs until first (extra) yield extrares = co() -- runs until first (extra) yield
end end
local res = table.pack(co()) -- runs until yield inside '__close' local res = table.pack(co()) -- runs until "regular" yield
assert(res.n == 2 and res[2] == nil) -- 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 local res2 = table.pack(co()) -- runs until end of function
assert(res2.n == t.n) assert(res2.n == t.n)
for i = 1, #t do for i = 1, #t do
@@ -879,10 +907,10 @@ do
end end
local function foo () local function foo ()
local x <close> = func2close(coroutine.yield) local x <close> = func2close(coroutine.yield) -- "regular" yield
local extra <close> = func2close(function (self) local extra <close> = func2close(function (self)
assert(self == extrares) assert(self == extrares)
coroutine.yield(100) coroutine.yield(100) -- first (extra) yield
end) end)
extrares = extra extrares = extra
return table.unpack{10, x, 30} return table.unpack{10, x, 30}
@@ -891,21 +919,21 @@ do
assert(extrares == 100) assert(extrares == 100)
local function foo () local function foo ()
local x <close> = func2close(coroutine.yield) local x <close> = func2close(coroutine.yield) -- "regular" yield
return return
end end
check(foo, false) check(foo, false)
local function foo () local function foo ()
local x <close> = func2close(coroutine.yield) local x <close> = func2close(coroutine.yield) -- "regular" yield
local y, z = 20, 30 local y, z = 20, 30
return x return x
end end
check(foo, false, "x") check(foo, false, "x")
local function foo () local function foo ()
local x <close> = func2close(coroutine.yield) local x <close> = func2close(coroutine.yield) -- "regular" yield
local extra <close> = func2close(coroutine.yield) local extra <close> = func2close(coroutine.yield) -- extra yield
return table.unpack({}, 1, 100) -- 100 nils return table.unpack({}, 1, 100) -- 100 nils
end end
check(foo, true, table.unpack({}, 1, 100)) check(foo, true, table.unpack({}, 1, 100))