Răsfoiți Sursa

'__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.
Roberto Ierusalimschy 5 luni în urmă
părinte
comite
127a8e80fe
4 a modificat fișierele cu 60 adăugiri și 27 ștergeri
  1. 0 4
      ldo.c
  2. 20 12
      lfunc.c
  3. 4 3
      manual/manual.of
  4. 36 8
      testes/locals.lua

+ 0 - 4
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 */

+ 20 - 12
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);
 }

+ 4 - 3
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

+ 36 - 8
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))