Using Luau types with metatables?

had this problem a while ago, gave up on it completely later. is it possible to make typedefs play nice with metatables? I’m not sure how to get it to work and couldn’t find it anywhere. in fact, the mere existence of a metatable throws the type checker off.

--!strict
type T = {
	x: number,
}

local t1: T = {x = 5} -- :)
local t2: T = setmetatable({x = 5}, {}) -- :(

thanks.

4 Likes

You can use assertion maybe, not sure if it’s exactly what you’re looking for though:

local t1 = {x = 5} :: T
local t2 = setmetatable({x = 5}, {}) :: T

It’ll still have the new autofill thing (which is in beta) if you’re looking for that. Only thing is that it might look a bit wonky since your types are asserted at the end of the line instead of at the variable declaration

1 Like

thanks, forgot about that. not exactly what I was looking for but it gets the job done. I wonder if metatables ever will be supported though? I’m surprised that nobody has brought this up before. if anybody ever finds a solution or linter update, feel free to reply here.

edit: update on that solution, for anyone reading. this will work when you use it in other places but the declaration will still show as up as red. better than nothing.

1 Like

I would hope so, given that a context-aware autocomplete is currently in the works, seeing as metatables are a crucial part of OOP and OOP is a crucial part of a lot of devs’ experience in developing.

This is supported syntax, I’ve still had nightmares getting it to work though

type meta = typeof(getMeta()), it’d still be nice for an @metatable keyword or smth

4 Likes

didn’t know you could use typeof. thought “why isn’t that in the docs?” and it was. c’est la vie. that was exactly what I was looking for, but I agree that a strict @metatable keyword would be a lot cleaner in many situations. thanks.

unfortunately this also causes issues with circular requires where two modules require each other’s types, since it’s impossible to create types without the actual constructors. guess I’ll leave it for now!

I didn’t either, good to know. But I’m confused, is getMeta a built in function I’ve never heard of? Or what should it return?

I think they mean getmetatable(t) (the global). for the actual table plus the metatable, you can use typeof(t) with the metatable already attached, and you’ll get a proper type. unfortunately it seems to be the only way to link them together.

1 Like

Ohh gotcha, that makes sense. Yeah, an @metatable keyword would be good too, or at least properly document it. It’s weird how obscure it is as I’ve never seen that before.

1 Like

agreed, there is only one reference to typeof in the docs which I found here, which was easy to miss

it sort of comes out of the blue, and it’s only used to explain something else!

1 Like

Sorry for the huge bump. I don’t quite understand how to use type meta = typeof(getMeta()) in my code. Is it possible for you to elaborate a bit? Thanks.

When you use typeof in a type declaration, it calls the function wrapped and then assign a type dynamically based on what it returns

function tab(n)
  if n == 1 then
    return {foo = "bar"}
  else
    return {bar = "foo"}
  end
end

type h = typeof(tab(1)) --> {foo: string}
type g = typeof(tab(2)) --> {bar: string}
2 Likes

How would I use that when creating a metatable?:

--!strict

local object = {}
object.__index = object
function object:FooBar()
	print("FooBar'ed")
end

type _construct = {
	foo: number,
	bar: string
}

local service = {}
function service.Create(): _construct & typeof(object) -- If I remove this it wont be invalid
	local construct = {}:: _construct -- valid
	construct.foo = 1 -- valid
	construct.bar = "s" -- valid

	return setmetatable(construct, object) -- invalid
end

Old post but here’s how I think you’d approach it (correct me if I’m wrong as I could be), I’m not getting any typescript errors:

--!strict

local object = {}

object.__index = object

local function getType()
	return setmetatable({
		name = 'someName'	
	}, object)
end

type object = typeof(getType())

function object.new() : object
	local t = {
		name = 'someName'
	}
	return setmetatable(t, object) -- no errors
end

If I return something else, I get an error,

1 Like

Ah, I’ve never actually thought of doing what you did. This is actually a very interesting concept. Thanks for the idea! It would be more convenient if we could just do it within one variable though.

1 Like

I find the approach below easier to remember and slightly less terrifying. I know it’s a late response, but thought it might help the next poor soul.

--!strict

local Rock = {}
Rock.__index = Rock

export type Rock = typeof(setmetatable({}, {})) & {
    getDensity: () -> number
}

function Rock.new() : Rock
    local self = {
        density = 77
    }
    local self = setmetatable(self :: any, Rock)

    return self
end

function Rock:getDensity()
    return self.density
end

return Rock
18 Likes

I know this is a considerably late reply, but I feel as if I have found some notable additional details.

When using the above approach, I would recommend adding in the parameters’ types of the class to the type definition.

export type Rock = typeof(setmetatable({}, {})) & {
	density: number --add this to the type

	getDensity: () -> number
}

The reason I say this is because when adding methods that use operations, Studio will give you a warning that the variable type is unknown.

function Rock:increaseDensity()
	self.density += 1 --Gives you an unknown type error
end

The way to resolve this is to change the “:” to a “.” in the method declaration. You would then specify the type of “self”, which only works if the parameter referenced in the method is in the type definition (as mentioned above).

function Rock.increaseDensity(self: Rock)
	self.density += 1 --no errors given
end

Note that calling the method would be the same since all the colon does is pass whatever is calling the method as the first parameter.

myRock:increaseDensity()
--is the same as
myRock.increaseDensity(myRock)

The reason you have to change the colon to a period when declaring the method itself is because you have to be able to specify the type of “self”.

There might be some way to do the same thing with typecasting, but I’m not as familiar with that so I’m not sure.