浏览代码

Debug information about extra arguments from __call

'debug.getinfo' can return number of extra arguments added to a call by
a chain of __call metavalues. That information is being used to improve
error messages about errors in these extra arguments.
Roberto Ierusalimschy 8 月之前
父节点
当前提交
50c7c915ee
共有 9 个文件被更改,包括 83 次插入12 次删除
  1. 16 8
      lauxlib.c
  2. 3 1
      ldblib.c
  3. 9 1
      ldebug.c
  4. 4 0
      ltests.c
  5. 1 0
      lua.h
  6. 11 2
      manual/manual.of
  7. 11 0
      testes/calls.lua
  8. 3 0
      testes/db.lua
  9. 25 0
      testes/errors.lua

+ 16 - 8
lauxlib.c

@@ -170,19 +170,27 @@ LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1,
 
 LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) {
   lua_Debug ar;
+  const char *argword;
   if (!lua_getstack(L, 0, &ar))  /* no stack frame? */
     return luaL_error(L, "bad argument #%d (%s)", arg, extramsg);
-  lua_getinfo(L, "n", &ar);
-  if (strcmp(ar.namewhat, "method") == 0) {
-    arg--;  /* do not count 'self' */
-    if (arg == 0)  /* error is in the self argument itself? */
-      return luaL_error(L, "calling '%s' on bad self (%s)",
-                           ar.name, extramsg);
+  lua_getinfo(L, "nt", &ar);
+  if (arg <= ar.extraargs)  /* error in an extra argument? */
+    argword =  "extra argument";
+  else {
+    arg -= ar.extraargs;  /* do not count extra arguments */
+    if (strcmp(ar.namewhat, "method") == 0) {  /* colon syntax? */
+      arg--;  /* do not count (extra) self argument */
+      if (arg == 0)  /* error in self argument? */
+        return luaL_error(L, "calling '%s' on bad self (%s)",
+                               ar.name, extramsg);
+      /* else go through; error in a regular argument */
+    }
+    argword = "argument";
   }
   if (ar.name == NULL)
     ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : "?";
-  return luaL_error(L, "bad argument #%d to '%s' (%s)",
-                        arg, ar.name, extramsg);
+  return luaL_error(L, "bad %s #%d to '%s' (%s)",
+                       argword, arg, ar.name, extramsg);
 }
 
 

+ 3 - 1
ldblib.c

@@ -191,8 +191,10 @@ static int db_getinfo (lua_State *L) {
     settabsi(L, "ftransfer", ar.ftransfer);
     settabsi(L, "ntransfer", ar.ntransfer);
   }
-  if (strchr(options, 't'))
+  if (strchr(options, 't')) {
     settabsb(L, "istailcall", ar.istailcall);
+    settabsi(L, "extraargs", ar.extraargs);
+  }
   if (strchr(options, 'L'))
     treatstackoption(L, L1, "activelines");
   if (strchr(options, 'f'))

+ 9 - 1
ldebug.c

@@ -352,7 +352,15 @@ static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar,
         break;
       }
       case 't': {
-        ar->istailcall = (ci != NULL && (ci->callstatus & CIST_TAIL));
+        if (ci != NULL) {
+          ar->istailcall = !!(ci->callstatus & CIST_TAIL);
+          ar->extraargs =
+                   cast_uchar((ci->callstatus & MAX_CCMT) >> CIST_CCMT);
+        }
+        else {
+          ar->istailcall = 0;
+          ar->extraargs = 0;
+        }
         break;
       }
       case 'n': {

+ 4 - 0
ltests.c

@@ -1900,6 +1900,10 @@ static struct X { int x; } x;
     else if EQ("closeslot") {
       lua_closeslot(L1, getnum);
     }
+    else if EQ("argerror") {
+      int arg = getnum;
+      luaL_argerror(L1, arg, getstring);
+    }
     else luaL_error(L, "unknown instruction %s", buff);
   }
   return 0;

+ 1 - 0
lua.h

@@ -504,6 +504,7 @@ struct lua_Debug {
   unsigned char nups;	/* (u) number of upvalues */
   unsigned char nparams;/* (u) number of parameters */
   char isvararg;        /* (u) */
+  unsigned char extraargs;  /* (t) number of extra arguments */
   char istailcall;	/* (t) */
   int ftransfer;   /* (r) index of first value transferred */
   int ntransfer;   /* (r) number of transferred values */

+ 11 - 2
manual/manual.of

@@ -4850,6 +4850,7 @@ typedef struct lua_Debug {
   unsigned char nups;         /* (u) number of upvalues */
   unsigned char nparams;      /* (u) number of parameters */
   char isvararg;              /* (u) */
+  unsigned char extraargs;    /* (t) number of extra arguments */
   char istailcall;            /* (t) */
   int ftransfer;              /* (r) index of first value transferred */
   int ntransfer;              /* (r) number of transferred values */
@@ -4938,6 +4939,14 @@ true if this function invocation was called by a tail call.
 In this case, the caller of this level is not in the stack.
 }
 
+@item{@id{extraargs}|
+The number of extra arguments added by the call
+to functions called through @idx{__call} metamethods.
+(Each @idx{__call} metavalue adds a single extra argument,
+the object being called,
+but there may be a chain of @idx{__call} metavalues.)
+}
+
 @item{@id{nups}|
 the number of upvalues of the function.
 }
@@ -5045,7 +5054,7 @@ fills in the fields @id{source}, @id{short_src},
 @id{linedefined}, @id{lastlinedefined}, and @id{what};
 }
 
-@item{@Char{t}| fills in the field @id{istailcall};
+@item{@Char{t}| fills in the fields @id{istailcall} and @id{extraargs};
 }
 
 @item{@Char{u}| fills in the fields
@@ -7993,7 +8002,7 @@ returns @fail plus the position of the first invalid byte.
 
 @LibEntry{utf8.offset (s, n [, i])|
 
-Returns the the position of the @id{n}-th character of @id{s}
+Returns the position of the @id{n}-th character of @id{s}
 (counting from byte position @id{i}) as two integers:
 The index (in bytes) where its encoding starts and the
 index (in bytes) where it ends.

+ 11 - 0
testes/calls.lua

@@ -204,6 +204,17 @@ do   print"testing chains of '__call'"
     assert(Res[i][1] == i)
   end
   assert(Res[N + 1] == "a" and Res[N + 2] == "b" and Res[N + 3] == "c")
+
+  local function u (...)
+    local n = debug.getinfo(1, 't').extraargs
+    assert(select("#", ...) == n)
+    return n
+  end
+
+  for i = 0, N do
+    assert(u() == i)
+    u = setmetatable({}, {__call = u})
+  end
 end
 
 

+ 3 - 0
testes/db.lua

@@ -624,6 +624,9 @@ local function f (x)
     end
 end
 
+assert(debug.getinfo(print, 't').istailcall == false)
+assert(debug.getinfo(print, 't').extraargs == 0)
+
 function g(x) return f(x) end
 
 function g1(x) g(x) end

+ 25 - 0
testes/errors.lua

@@ -117,6 +117,31 @@ else
     return 1
   ]]
   assert(string.find(res, "xuxu.-main chunk"))
+
+  do   -- tests for error messages about extra arguments from __call
+    local function createobj (n)
+      -- function that raises an error on its n-th argument
+      local code = string.format("argerror %d 'msg'", n)
+      local func = T.makeCfunc(code)
+      -- create a chain of 2 __call objects
+      local M = setmetatable({}, {__call = func})
+      M = setmetatable({}, {__call = M})
+      -- put it as a method for a new object
+      return {foo = M}
+    end
+
+  _G.a = createobj(1)   -- error in first (extra) argument
+  checkmessage("a:foo()", "bad extra argument #1")
+
+  _G.a = createobj(2)   -- error in second (extra) argument
+  checkmessage("a:foo()", "bad extra argument #2")
+
+  _G.a = createobj(3)   -- error in self (after two extra arguments)
+  checkmessage("a:foo()", "bad self")
+
+  _G.a = createobj(4)  -- error in first regular argument (after self)
+  checkmessage("a:foo()", "bad argument #1")
+  end
 end