How would I make metatable not ignore __lt

I need to compare metatables but doing this (on Roblox Lua)

local a = setmetatable({ b = 1 }, { __lt = function(self, other) return self.b < other; end; });
print(a < 2);

returns this

18:37:44.861 - LocationOfTheScript:3: attempt to compare table and number
18:37:44.861 - Stack Begin
18:37:44.861 - Script ‘LocationOfTheScript’, Line 3
18:37:44.861 - Stack End

How would I make Lua not ignore __lt when doing Metatable < Value?

This works on Lua 5.3 some reason.

local a = setmetatable({ b = 1 }, { __lt = function(self, other) return self.b < other; end; });
print(a < 2);

true

But not on Roblox Lua.

I did some testing, including using newproxy as I hoped it would allow you to override the __lt function (newproxy allows you to override the __len metamethod so I was hopeful). Code used:

local Class = { }

function Class.new(value)
	local this = {
		value = value
	}
	
	local obj  = newproxy(true)
	local meta = getmetatable(obj)
	
	meta.__index = this
	meta.__lt    = (function (self, b)
		if type(b) == "number" then
			return self.value < b
		else
			error(("Cannot compare [Class] with type %s").format(type(b)))
		end
	end)
	
	return obj
end

local n = Class.new(2)
print(
	n < 3
)

Unfortunately, I ran into the same issue as you. It seems it isn’t possible to override the __lt operator with the current version of Lua RBX uses (or rather, their compiler?).

1 Like

The script works exactly as it should; Roblox is not “hiding” the functionality of __lt.

Check out the note on the bottom of this Roblox article on metatables:

This applies to __lt, __le, and __eq:
¹ Requires two values with the same metatable and basic type (table/userdata/etc.); does not work with a table and another random table, or with a userdata and a table.

Edit: The devhub note is slightly incorrect. The two values do not necessarily have to have the same metatable; they just need to have the same metamethod.

Your error is expected as it does not meet the condition of having two objects with similar datatypes and similar metamethods. Lua 5.3 runs it correctly because it handles the metamethods differently (for those curious, this difference can be seen in the different implementations for call_orderTM and luaT_callorderTM in the Lua 5.1/5.3 source, respectively), but for Lua 5.1, your code will not run the way you want it to.

Consider this example (the results are the same on Roblox’s Luau and Lua 5.1 as Roblox does not modify this functionality for consistency).

local mt1 = {__lt = function(self, other) return self.b < other.b end}
local mt2 = {__lt = function(self, other) return self.b < other.b end}
local mt3 = {__lt = mt1.__lt}

local userdata1 = newproxy(true)
local userdata2 = newproxy(true)

local table1 = setmetatable({b = 1}, mt1)
local table2 = setmetatable({b = 2}, mt1)

local table3 = setmetatable({b = 2}, mt2) -- note the mt2
local table4 = setmetatable({b = 2}, mt3) -- note the mt3

getmetatable(userdata1).__index = {b = 1}
getmetatable(userdata2).__index = {b = 2}
getmetatable(userdata1).__lt = mt1.__lt    -- note the mt1
getmetatable(userdata2).__lt = mt1.__lt

print(table1 < table2) --> true; table1's __lt metamethod equals table2's __lt metamethod
print(table1 < table4) --> true; table1's __lt metamethod equals table2's __lt metamethod
print(userdata1 < userdata2) --> true; both are userdatas and userdata1's __lt metamethod equals userdata1's __lt metamethod
print(table1 < table3) --> error; table1's __lt metamethod is different from table2's __lt metamethod
2 Likes

Actually your example doesn’t error. As long as getmetatable(a).__lt(a, b) == getmetatable(b).__lt(a, b) evaluates to true then this will work. This is how it works for all relational metamethods

1 Like

Yeah you’re right. As long as the metamethods are the same (not necessarily the metatables), no error will occur. I’ve edited my original post to reflect this.

However, in my example, the __lt metamethods point to different functions, so it still errors.