2

Here's a potential pattern that can check if an argument is a table:

int my_fn(lua_State *L) {
  luaL_checktype(L, 1, LUA_TTABLE);
  // .. do stuff with the table ..
}

This works whenever the first argument is a table. However, other Lua types support table lookups, such as a userdata, and in luajit, a cdata.

Is there a nice way to check if a table lookup, such as via lua_getfield, will succeed before I call it? I mean without restricting the type to tables. Relatedly, are tables, userdata, and cdata the only types in luajit that support indexed lookups?

I'm most interested in answers restricted to the Lua 5.1 C API because I'm using LuaJIT which currently works with this version.

Clarification

The advantage of the luaL_checkXXX functions is that, in one line, they:

  • throw an informative, user-friendly error message if the type is wrong, and
  • provide a C-friendly return value that can be used immediately.

I'm looking for something similar for tables. I don't expect a C-friendly hash-table return value, but I do want the same quality of error message to the user if the argument in question is not indexable.

I'm embracing the philosophy of duck typing. If I write a function that simply wants to index some keys from an argument, then I don't care if that argument is truly a table, or just a userdata that supports __index lookups. I want to accept either one.

hjpotter92
  • 78,589
  • 36
  • 144
  • 183
Tyler
  • 28,498
  • 11
  • 90
  • 106
  • 1
    What do you want to do if the argument doesn't support indexing? Raise an error?! If so, just index it and let Lua generate a suitable error message. For userdata you could use [`luaL_getmetafield()`](http://www.lua.org/manual/5.1/manual.html#luaL_getmetafield) to check beforehand whether an `__index` metamethod is defined, but that metamethod could raise an error anyway, so you are no better off than just trying to index the userdata. – siffiejoe Sep 26 '15 at 05:01
  • @siffiejoe I added a clarification in response to your comment. I've also provided an example answer which is the best I currently know of. Even though an `__index` lookup could throw an error, I still find it worthwhile to check to see if an argument is indexable at all. In other words, I expect most `__index` lookups to succeed, and can provide better error behavior to users in that typical scenario. – Tyler Sep 29 '15 at 19:08

2 Answers2

0

In general, just tables have lookups because it's the only type which defines this property. Userdata are opaque, just the host knows what to do with it or adds a metatable (which can be analyzed) for specific behaviour. CData are part of Lua compiling with LuaJIT, i never used this type with C API (is it even supported?). At the end you have to check the type/metatable for possible lookups and request a field to check for setting, there's no way around lua_getfield (but raw access should be faster, see lua_rawget). The exception would be to check for table array length by lua_objlen.

Furthermore a cheaper solution for type checking would be lua_is*** functions.

Youka
  • 2,646
  • 21
  • 33
  • Thanks, @Youka. I realize only tables provide index lookups by default, but we can take a read-only look at the metatable of a userdata value to see if `__index` exists. I also find the error-message behavior of `luaL_checkXXX` to be more useful than just calling a `lua_isXXX` function. I've elaborated the question and added an example answer in response to you and @siffiejoe. – Tyler Sep 29 '15 at 19:11
0

Here's one way to do it:

// If the value at index narg is not indexable, this function does not return and
// provides a user-friendly error message; otherwise the stack is unchanged.
static void luaL_checkindexable(lua_State *L, int narg) {
  if (lua_istable(L, narg)) return;  // tables are indexable.
  if (!luaL_getmetafield(L, narg, "__index")) {
    // This function will show the user narg and the Lua-visible function name.
    luaL_argerror(L, narg, "expected an indexable value such as a table");
  }
  lua_pop(L, 1);  // Pop the value of getmetable(narg).__index.
}

This works for tables and any value with an __index value on its metatable.

It provides a standard-format error given by luaL_argerror. Here's an example error message:

a_file.lua:7: bad argument #1 to 'fn' (expected an indexable value such as a table)

You can use it like this:

// This Lua-facing function expects an indexable 1st argument.
int my_fn(lua_State *L) {
  luaL_checkindexable(L, 1);
  lua_getfield(L, 1, "key");  // --> arg1.key or nil is now on top of stack.
  // .. your fn ..
}
Tyler
  • 28,498
  • 11
  • 90
  • 106