Using annotations inside a metamethod

To sum it up, I’m using --!strict because i like it, but the engine is shouting at me because I’m not using annotations on metamethods.
Here is an example:

HexModule.__add = function(hex1:ANNOTATION, hex2:ANNOTATION)
	return HexModule.new(hex1.q + hex2.q, hex1.r + hex2.r, hex1.s + hex2.s)
end

If I write coordinates in ANNOTATION, which is a type I made earlier, then the engine will shout at me whenever i try to actually use the metamethod. Here is an example:

print(hex1 + hex2)
--# Type Error: (16,8)
--# Type '{ @metatable HexModule, coordinates }'
--# could not be converted into 'coordinates'
--> this is a warning on the script editor, not an actual error <--

And then if I don’t use an annotation:

HexModule.__add = function(hex1, hex2)
	return HexModule.new(hex1.q + hex2.q, hex1.r + hex2.r, hex1.s + hex2.s)
end
--# Type Error: (34,23) Unknown type used in + operation;
--# consider adding a type annotation to 'hex1'
--> there are 3 of these warnings <--

Here is the coordinates type:

type coordinates = {
	q:number, r:number, s:number
}

Which technically is true… Because that metatable is not the same as a simple table. However I can’t find a way to do this other than using: (coordinates | any) as my ANNOTATION. I have more than one metamethod and that is really bulky.

It’s also worth mentioning that this is not an issue with every metamethod, only those which require the use of the table (q / r / s), like __add or __sub__tostring isn’t bothered by this even though I am using self.q / self.r / self.s

I feel like this is barely talked about and it’s quite interesting - which makes it almost impossible to find any information or people having the same issues.
Thanks to everyone for their patience, if you know anything let me know.

-Masak

For now I will make do with any. Which is not a real optimal solution but it works even though I lose the type checking feature and won’t have any suggestions.

This problem isn’t a big deal but There’s no information so I thought of making a post and see if I get anything :happy1:

I still need to familiarise myself with Luau a bit more, but I believe you should be able to silence it by using a type cast, like so:

HexModule.__add = function( hex1, hex2 )
	return HexModule.new(
		hex1.q :: number + hex2.q :: number,
		hex1.r :: number + hex2.r :: number,
		hex1.s :: number + hex2.s :: number
	)
end

This will/should override the type, essentially forcing the linter to recognise it as a number and not throw a fuss. I could be very wrong though.

Update on the issue:

  1. Another “it works” solution is to make another type that is equal to ( coordinates | any ). This kind of works but it still wont fix the next problem:
  2. cpos1 == cpos2 also throws a warning saying that they are not the same table and hence cannot be compared. (This again, works when running the script anyway) – I can’t exactly make the function .new() use : HexType because then it errors saying that the metatable could not be converted to HexType. Which again, makes sense because it is not a metatable and it would have any other function like .neighbor()

This actually makes the warning go away! But now that I have another type value that fixes that warning issue I’m good.

However I’m really concerned about what to do on the __eq error

Thanks for your idea

I wanted to make a quick example that is based on how type definitions are setup in a project I’m working on, which doesn’t seem to that many throw errors.

--!strict

-- Troop Type Definition
type Troop = {
	__index: Troop,
	__add: ( troop1: Troop, troop2: Troop ) -> Troop,
	new: (troopCount: number) -> Troop,

	TroopCount: number,
}

local Troop = {}
Troop.__index = Troop
Troop.__add = function( troop1: Troop, troop2: Troop ): Troop
	return Troop.new(troop1.TroopCount + troop2.TroopCount)
end

-- Constructor.
function Troop.new( troopCount: number ): Troop
	local self: Troop = setmetatable({} :: any, Troop)

	self.TroopCount = troopCount

	return self
end

local troop1 = Troop.new(5)
print("Troop 1", troop1, troop1.TroopCount)
local troop2 = Troop.new(13)
print("Troop 2", troop2, troop2.TroopCount)
-- This is the only issue I have, local troop3 issues a warning stating:
-- 'Binary operator '+' not supported by types 'Troop' and 'Troop''. However, it still works as expected.
local troop3 = troop1 + troop2
print("Troop 3", troop3, troop3.TroopCount)

Your issue might be for several reasons, but the main one is that you are setting Troop twice. IDK if this is an issue with the code you sent me but that’s what I can see

Edit: another issue I think might be causing the problem is that you are doing {} :: any – Honestly I’m not good with this either but that changes things.
I tried removing it and realised the same I did on mine: you cant set a metatable to TroopType because while it might be the same TroopType isn’t a metatable but a regular table.

sorry about so many edits. but…
Edit2: I found out a big problem with metatables. Type values can’t ever replicate a metatable (or at least i don’t know how), which leads me to believe that --!strict doesn’t really work when trying to make custom data types. Only work arounds can be done – I guess that’s fine? We should probably report this or get some insights/plans for the future --!strict is not used that often so i guess it makes sense that devs barely expand on this subject.

1 Like

Metatables and on strict mode is just lacking. However I can get around the warning by setting the the type to this MyType & any – Seems to do the trick, and also get the recommendations I want.

This is the only time something like this was mentioned: Type checking - Luau

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.