lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


I implemented __methindex once before and as I recall it was fairly simple. The key comes in changing the logic of the SELF opcode. That opcodes job is to return the method to call and the object. It's current implementation is essentially:

	return gettable_event( obj, methodkey ), obj

To implement __methindex, we change this to:

	return getmethod_event( obj, methodkey ), obj

(Side note: It might be interesting to also allow overriding the value of obj, but I'm not going there with this analysis.)

Here is the code for gettable_event:

	function gettable_event( table, key )
		local h
		if type( table ) == 'table' then
			local v = rawget( table, key )
			if v ~= nil then return v end
			h = metatable( table ).__index
			if h == nil then return nil end
		else
			h = metatable( table ).__index
			if h == nil then
				error( ... )
			end
		if type( h ) == "function" then
			return h( table, key )	-- call the handler
		else
			return h[ key ]		-- or repeat the operation on it
		end
	end

One simple definition for getmethod_event to add support for __methindex would be to simply look for this entry first and otherwise treat it just like __index:

	function getmethod_event( obj, mkey )
		local h
		if type( obj ) == 'table' then
			local v = rawget( obj, mkey )
			if v ~= nil then return v end
			local mt = metatable( obj )
			h = mt.__methodindex or mt.__index
			if h == nil then return nil end
		else
			local mt = metatable( obj )
			h = mt.__methodindex or mt.__index
			if h == nil then
				error( ... )
			end
		end
		if type( h ) == "function" then
			return h( obj, mkey )	-- call the handler
		else
			getmethod_event( h, mkey ) -- or repeat the operation on it
		end
	end

This makes a couple of choices that are subject to discussion, however:

* It doesn't solve the Set class problem because it puts the rawget ahead of the method table lookup. To avoid that, we would need to do something like the following:

	function getmethod_event( obj, mkey )
		local mt = metatable( obj )
		local h = mt.__methindex
		if h ~= nil then
			-- Fall through
		elseif type( obj ) == 'table' then
			local v = rawget( obj, mkey )
			if v ~= nil then return v end
			h = mt.__index
			if h == nil then return nil end
		elseif mt
			h = mt.__index
			if h == nil then
				error( ... )
			end
		end
		if type( h ) == "function" then
			return h( obj, mkey )	-- call the handler
		else
			getmethod_event( h, mkey ) -- or repeat the operation on it
		end
	end

Note, however, that this construction makes it impossible to override a method inherited via __methindex within the object itself. Solving the Set class problem more or less seems to mandate that limitation, however.

* I think there's probably room to debate whether we should continue following __methindex links after following an __index link.

Broader issues also need to be considered:

* This requires either library support -- mcall( obj, key, ... ) -- or syntactic support -- obj:[ key ]( ... ) -- for calling methods based on a variable rather than a constant. The syntactic support is fairly easy to add.

* It would benefit from support for creating bound methods either via a library call -- mbind( obj, mkey ) -- or via syntax obj:mkey. To get the full benefits of the metamethod, however, one does not want to allow generic method lookup returning a function which can be called elsewhere. There are distinct performance and conceptual benefits to knowing that a method function was only accessed via a method lookup on its first parameter.

* There are questions around whether there needs to be a corresponding assignment metamethod since one could no longer rely on the translation of

	function obj:method( ... ) ... end

into

	obj.method = function( self, ... ) ... end

As doing the right thing in the presence of __methindex and __newindex alone would not be able to overcome this since it would not know the context for the assignment.

Despite these details to resolve, I add a hearty +1 to the concept because I think it makes it easier to catch or block a variety of errors and exploits and it simplifies the process of trying to implement both methods and properties.

Mark