Thanks to the comments of the daniator, I think I found a way to make the "follow" __index as I want. It has condensed by this function:
local metamethods = { '__add', '__sub', '__mul', '__div', '__mod', '__pow', '__unm', '__concat', '__len', '__eq', '__lt', '__le', '__call', '__gc', '__tostring', '__newindex' } function setindirectmetatable(t, mt) for _,m in ipairs(metamethods) do rawset(mt, m, rawget(mt,m) or function(...) local supermt = getmetatable(mt) or {} local index = supermt.__index if(type(index)=='function') then return index(t,m)(...) end if(type(index)=='table') then return index[m](...) end return nil end) end return setmetatable(t, mt) end
Hope it's easy enough. When a new metatet is installed, it initializes it with all metamethods (without replacing the existing ones). These metamethods are ready to "pass" requests to the "parent meta tags."
This is the easiest solution I could find. Well, I actually found a solution that used fewer characters and was a bit faster, but it was about black magic (it involved metatable functions, de-references to itself), and it was much less readable than this.
If someone finds a shorter, simpler function that does the same, I will gladly give him the answer.
The usage is simple: replace setmetatable with setindirectmetatable if you want it to "go up":
mt1 = {__tostring=function(x) return x.name or "no name" end } mt2 = {} setmetatable(mt2, {__index=mt1}) x = {name='x'} y = {name='y'} setmetatable(x, mt1) setindirectmetatable(y, mt2) -- only change in code print(x) -- prints "x" print(mt2.__tostring(y)) -- prints "y" print(y) -- prints "y"
A small warning word: setindirectmetatable creates meta tags on mt2. Changing this behavior to make a copy while mt2 remains unchanged should be trivial. But letting them configure the default is actually better for my purposes.