commit 50c7c915ee2fa239043d5456237f5145d064089b
parent b117bdb3448778d9e7f9a0302791e8ac3bb97ddd
Author: Roberto Ierusalimschy <roberto@inf.puc-rio.br>
Date: Tue, 19 Nov 2024 14:08:51 -0300
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.
Diffstat:
9 files changed, 83 insertions(+), 12 deletions(-)
diff --git a/lauxlib.c b/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);
}
diff --git a/ldblib.c b/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'))
diff --git a/ldebug.c b/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': {
diff --git a/ltests.c b/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;
diff --git a/lua.h b/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 */
diff --git a/manual/manual.of b/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.
diff --git a/testes/calls.lua b/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
diff --git a/testes/db.lua b/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
diff --git a/testes/errors.lua b/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