Method injection#

Method injection lets you replace an existing method of a class with a new method, containing the original method as an upvalue. This can be used to change the behavior of a class method or to alter the __index or __newindex metamethods of a class to provide extra functionality.

The following example is taken from the unit tests. Suppose we want to provide an object with properties that have custom getters and setters. We’ll call our example property n. When we access n, such as in i = obj.n, we want to return the value of a getter method, get_n. When we set n, we want to call a setter method, set_n. The getter and setter methods are shown below.

static int get_n(lua_State *L) {
    lua_getfield(L, 1, "x");
    return 1;
}

static int set_n(lua_State *L) {
    int n = luaL_checknumber(L, 2);
    lua_pushnumber(L, n * 2);
    lua_setfield(L, 1, "x");
    return 0;
}

As you can see, the getter method simply returns our actual value, stored in x. The setter sets x to double the provided value.

We can add a properties table to our class, which will contain subtables for each property. Each subtable can contain a get method and a set method. Assuming our class is at the top of the stack:

lua_newtable(L);              // properties table
lua_newtable(L);              // property 'n'
lua_pushcfunction(L, get_n);  // getter for 'n'
lua_setfield(L, -2, "get");
lua_pushcfunction(L, set_n);  // setter for 'n'
lua_setfield(L, -2, "set");
lua_setfield(L, -2, "n");
lua_setfield(L, -2, "Properties");

Now we must create modified __index and __newindex metamethods that check our properties table before deferring to regular access. Note the usage of the special helper functions luaC_deferindex() and luaC_defernewindex(), which call the __index or __newindex function stored in the upvalue, respectively.

static int index_override(lua_State *L) {
    luaL_getmetafield(L, 1, "__class");
    if (lua_getfield(L, -1, "Properties") == LUA_TTABLE) {
        lua_pushvalue(L, 2);                      // push key
        if (lua_gettable(L, -2) == LUA_TTABLE &&  // check properties for key
            lua_getfield(L, -1, "get") == LUA_TFUNCTION) {
            lua_pushvalue(L, 1);  // push self
            lua_call(L, 1, 1);    // call getter
            return 1;
        }
    }
    luaC_deferindex(L);
    return 1;
}

static int newindex_override(lua_State *L) {
    luaL_getmetafield(L, 1, "__class");
    if (lua_getfield(L, -1, "Properties") == LUA_TTABLE) {
        lua_pushvalue(L, 2);                      // push key
        if (lua_gettable(L, -2) == LUA_TTABLE &&  // check properties for key
            lua_getfield(L, -1, "set") == LUA_TFUNCTION) {
            lua_pushvalue(L, 1);  // push self
            lua_pushvalue(L, 3);  // push value
            lua_call(L, 2, 0);    // call setter
            return 0;
        }
    }
    luaC_defernewindex(L);
    return 0;
}

To override the __index and __newindex metamethods on our class, we call the helper methods, luaC_injectindex and luaC_injectnewindex.

luaC_injectnewindex(L, -1, newindex_override);
luaC_injectindex(L, -1, index_override);

And that’s it! Accessing n will now defer to our accessor methods in our property table, while accessing any field that doesn’t have an entry in our property table will just go through our usual metamethods. Note that any method can be overridden with method injection, not just __index and __newindex (see luaC_injectmethod()).