mirror of
https://github.com/kikito/middleclass.git
synced 2024-11-08 09:34:22 +00:00
Implement inheritable metamethods without stubs
In __newindex metamethods of each Class detect if a metamethod is assigned. In that case, propogate assignment to all child classes that do not overwrite the metamethod. Track set of metamethods each class defines itself. On subclassing, copy all parent metamethods to child. Benefits of this approach: * Objects only have metamethods when they are, in fact, defined. Ignoring overhead, the problem with stubs is that other libraries, seeing a stub metamethod, could try to call it and get an error if it's missing in the class. E.g. various deep comparison functions often try to detect and use __eq. * Non-function metafields such as __metatable can be used. * Less overhead when using metamethods (but more when defining them).
This commit is contained in:
parent
e7d8bc00d6
commit
d9f00351b2
@ -42,17 +42,51 @@ local function _setClassDictionariesMetatables(aClass)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local all_metamethods_list = { '__add', '__band', '__bor', '__bxor', '__bnot', '__call', '__concat',
|
||||||
|
'__div', '__eq', '__ipairs', '__idiv', '__le', '__len', '__lt', '__mod',
|
||||||
|
'__mul', '__pairs', '__pow', '__shl', '__shr', '__sub', '__tostring', '__unm' }
|
||||||
|
|
||||||
|
local all_metamethods = {}
|
||||||
|
|
||||||
|
for _, metamethod in ipairs(all_metamethods_list) do
|
||||||
|
all_metamethods[metamethod] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _propogateMetamethod(aClass, key, value)
|
||||||
|
for subclass in pairs(aClass.subclasses) do
|
||||||
|
if not subclass.__metamethods[key] then
|
||||||
|
subclass.__instanceDict[key] = value
|
||||||
|
_propogateMetamethod(subclass, key, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _updateClassDict(aClass, key, value)
|
||||||
|
if all_metamethods[key] then
|
||||||
|
if value == nil then
|
||||||
|
aClass.__metamethods[key] = nil
|
||||||
|
value = aClass.super and aClass.super.__instanceDict[key]
|
||||||
|
else
|
||||||
|
aClass.__metamethods[key] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
_propogateMetamethod(aClass, key, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
aClass.__instanceDict[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
local function _setClassMetatable(aClass)
|
local function _setClassMetatable(aClass)
|
||||||
setmetatable(aClass, {
|
setmetatable(aClass, {
|
||||||
__tostring = function() return "class " .. aClass.name end,
|
__tostring = function() return "class " .. aClass.name end,
|
||||||
__index = aClass.static,
|
__index = aClass.static,
|
||||||
__newindex = aClass.__instanceDict,
|
__newindex = _updateClassDict,
|
||||||
__call = function(self, ...) return self:new(...) end
|
__call = function(self, ...) return self:new(...) end
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _createClass(name, super)
|
local function _createClass(name, super)
|
||||||
local aClass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict={} }
|
local aClass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict = {}, __metamethods = {} }
|
||||||
aClass.subclasses = setmetatable({}, {__mode = "k"})
|
aClass.subclasses = setmetatable({}, {__mode = "k"})
|
||||||
|
|
||||||
_setClassDictionariesMetatables(aClass)
|
_setClassDictionariesMetatables(aClass)
|
||||||
@ -61,17 +95,9 @@ local function _createClass(name, super)
|
|||||||
return aClass
|
return aClass
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _createLookupMetamethod(aClass, name)
|
local function _setSubclassMetamethods(aClass, subclass)
|
||||||
return function(...)
|
for m in pairs(aClass.__metamethods) do
|
||||||
local method = aClass.super[name]
|
subclass.__instanceDict[m] = aClass.__instanceDict[m]
|
||||||
assert( type(method)=='function', tostring(aClass) .. " doesn't implement metamethod '" .. name .. "'" )
|
|
||||||
return method(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function _setClassMetamethods(aClass)
|
|
||||||
for _,m in ipairs(aClass.__metamethods) do
|
|
||||||
aClass[m]= _createLookupMetamethod(aClass, m)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -97,10 +123,6 @@ end
|
|||||||
|
|
||||||
local Object = _createClass("Object", nil)
|
local Object = _createClass("Object", nil)
|
||||||
|
|
||||||
Object.static.__metamethods = { '__add', '__band', '__bor', '__bxor', '__bnot', '__call', '__concat',
|
|
||||||
'__div', '__eq', '__ipairs', '__idiv', '__le', '__len', '__lt', '__mod',
|
|
||||||
'__mul', '__pairs', '__pow', '__shl', '__shr', '__sub', '__tostring', '__unm' }
|
|
||||||
|
|
||||||
function Object.static:allocate()
|
function Object.static:allocate()
|
||||||
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
|
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
|
||||||
return setmetatable({ class = self }, self.__instanceDict)
|
return setmetatable({ class = self }, self.__instanceDict)
|
||||||
@ -117,7 +139,7 @@ function Object.static:subclass(name)
|
|||||||
assert(type(name) == "string", "You must provide a name(string) for your class")
|
assert(type(name) == "string", "You must provide a name(string) for your class")
|
||||||
|
|
||||||
local subclass = _createClass(name, self)
|
local subclass = _createClass(name, self)
|
||||||
_setClassMetamethods(subclass)
|
_setSubclassMetamethods(self, subclass)
|
||||||
_setDefaultInitializeMethod(subclass, self)
|
_setDefaultInitializeMethod(subclass, self)
|
||||||
self.subclasses[subclass] = true
|
self.subclasses[subclass] = true
|
||||||
self:subclassed(subclass)
|
self:subclassed(subclass)
|
||||||
|
@ -171,7 +171,6 @@ describe('Metamethods', function()
|
|||||||
|
|
||||||
describe('An instance', function()
|
describe('An instance', function()
|
||||||
it('has a tostring metamethod, returning a different result from Object.__tostring', function()
|
it('has a tostring metamethod, returning a different result from Object.__tostring', function()
|
||||||
assert.not_equal(Peter.__tostring, Object.__tostring)
|
|
||||||
assert.equal(tostring(peter), 'instance of class Peter')
|
assert.equal(tostring(peter), 'instance of class Peter')
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
Loading…
Reference in New Issue
Block a user