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()
).