lua

A copy of the Lua development repository
Log | Files | Refs | README

commit 127a8e80fe0d74efd26994b3877cdc77b712ea56
parent f9e35627ed26dff4114a1d01ff113d8b4cc91ab5
Author: Roberto Ierusalimschy <roberto@inf.puc-rio.br>
Date:   Fri, 28 Feb 2025 10:10:00 -0300

'__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.

Diffstat:
Mldo.c | 4----
Mlfunc.c | 32++++++++++++++++++++------------
Mmanual/manual.of | 7++++---
Mtestes/locals.lua | 44++++++++++++++++++++++++++++++++++++--------
4 files changed, 60 insertions(+), 27 deletions(-)

diff --git a/ldo.c b/ldo.c @@ -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 */ diff --git a/lfunc.c b/lfunc.c @@ -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); } diff --git a/manual/manual.of b/manual/manual.of @@ -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 diff --git a/testes/locals.lua b/testes/locals.lua @@ -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))