custom __concat metamethod gets ignored in typecheck (new typesolver)

“type ‘string’ could not be converted into ‘class’”
Example code:

--!strict

type classData = {
	b:buffer;
	len:number;
}
local metatable = {
	__concat = function(self:class,str:string):class
		buffer.writestring(self.b,self.len,str)
		self.len+=#str
		return self
	end;
}

export type class = typeof(setmetatable({}::classData, metatable))

--returns a long string
local new = function():class
	return setmetatable({
		b = buffer.create(100_000::number);
		len = 0;
	}::classData,metatable)::class
end
local class = new()

class..="Hello"

BUT casting it to “any” absolutelly solves linting proving my theory that it is a problem for custom defined metamethods

local class:class|any = new()
class..="Hello"

I tried using setmetatable<classData,typeof(metatable)> but result is the same.

1 Like

Hi,

Late to the show here, but I was digging into this problem myself and it’s worth noting that the order of __tostring’s args do actually change around depending on how the concatenation is set up, specifically whether or not it’s followed by any further concatenation; regardless, checking which is the table type and which is the string type has been the most straightforward approach for me.

Here’s how I apply it in a basic setting:

type MyData = {
	Summary: string
}

local MT = {
	__concat = function(pre: any, post: any): string
		if (type(post) == "table") then
			return (pre or "") .. (post :: MyType).Summary
		end

		return (pre :: MyType).Summary .. (post or "")
	end
}

--[[ Or shorter if that's your thing
local MT = {
	__concat = function(pre: any, post: any)
		return (pre.Summary or pre) .. (post.Summary or post)
	end
}
]]

type MyType = setmetatable<MyData,typeof(MT)>

local myTestInstance: MyType = setmetatable({
	Summary = "Foo"
},MT)

print(myTestInstance) -- This is where a __tostring implementation would be more appropriate
print("--> " .. myTestInstance)
print(myTestInstance .. " <--")
print("--> " .. myTestInstance .. " <--")

image

Incidentally, my typechecker would not stop complaining until I had __tostring written in this manner.

Most examples online just do some variety of table concatenation/basic prints and don’t bother with all the different ways you can concatenate something (though these are hardly edge cases…), but naturally when working with custom types you want a little more control over what actually gets printed.

Anyway, I’m a little confused about how you’re looking to use buffers in this example, but here is a proposed adjustment nonetheless:

local metatable = {
	__concat = function(pre:any,post:any): class
		local class: class = if type(pre) == "table" then pre else post
		local str = if type(pre) == "string" then pre else post
		
		buffer.writestring(class.b,class.len,str)
		class.len+=#str
		return class
	end;
}

Let me know if this helps! Sorry for the necro but having found an important detail it seemed notable enough to post.