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}, {}) -- :(
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
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.
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.
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 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.
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.
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}
--!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
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.
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
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.