Search code examples
clualuajitlua-api

When writing a Lua-facing function in C, what's a good way to check if an argument supports table-like lookups?


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.


Solution

  • 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 ..
    }