The Type-Checking for setmetatable bugs out when you try to use another table with a metatable attached ({@ metatable t, m}), preventing the autocompleter from working properly, especially when using the __index metamethod.
Repro
--!strict
local Foo = {}
Foo.__index = Foo
Foo.A = 1
function new1()
return setmetatable({}, Foo)
end
local Bar = {}
Bar.__index = Bar
Bar.B = 2
setmetatable(Bar, Foo)
function new2()
return setmetatable(new1(), Bar) -- 'setmetatable should take a table'
end
print(new2().B) -- Bugged
Additional Info
This is the specific bug that prevents writing Strictly-Typed OOP as it prevents the SubClass type from reading methods from itself in the below examples.
SuperClass (Not Bugged)
--!strict
local SuperClass = {}
SuperClass.__index = SuperClass
type self = {
SuperValue: number
}
export type SuperClass = typeof(setmetatable({} :: self, SuperClass))
function SuperClass.new(): SuperClass
local self: SuperClass = setmetatable({} :: self, SuperClass)
self.SuperValue = 1
return self
end
function SuperClass:Foo(): ()
local self: SuperClass = self
print("Foo")
end
return SuperClass
SubClass
--!strict
local SuperClass = require(SuperClass)
local SubClass = {}
SubClass.__index = SubClass
setmetatable(SubClass, SuperClass)
type self = SuperClass.SuperClass & {
SubValue: number
}
export type SubClass = typeof(setmetatable({} :: self, SubClass))
function SubClass.new(): SubClass
local self = setmetatable(SuperClass.new() :: self, SubClass)
self.SuperValue = 1
self.SubValue = 1
self:Bar() -- Nope
return self
end
function SubClass:Bar(): ()
local self: SubClass = self
print("Bar")
end
return SubClass
Checking on this two and a half months later. This bug is still present and interfering with my workflow.
The problem is most likely caused by normal tables not being able to have the type of a table with a metatable casted onto it. This would explain why setmetatable doesn’t like the forementioned type.
--!nonstrict
local Table = {} :: typeof(setmetatable({}, {}))
Sorry the reviving the topic and notifying you all, but did any of you guys find a solution to the problem? This seems to be the only explanation to the issue I’m experiencing right now. Do you guys have a work-around to circumvent this issue, or should I just stop using type checking in a script that encounters this bug?
The new type solver seems to fix the first half of the problem but not the second. It looks like something is messing up with the __index metamethod:
local foo = {}
foo.__index = foo
foo.a = 1
function f()
return setmetatable({}, foo)
end
local bar = {}
bar.__index = bar
bar.b = 2
function g()
return setmetatable(f(), bar)
end
local a = f().a
local b = g().b -- autocompletes, but warns
Just cast to any for every false positive. I wouldn’t recommend turning off typechecking.
When I read your example script, I was in a bit of disbelief, so I decided to check it out for myself. For whatever reason, when I put the script in studio the issue is still there, unless I’m not understanding what you meant by “the first half of the problem”.
I’m still experiencing the same “setmetatable should take a table” warning. And although Bar objects are able to autocomplete the values of Foo, they’re unable to autocomplete the Values of bar.
Was your “first half of the problem” related to Bar being unable to autocomplete Foo?
--!strict
local foo = {}
foo.__index = foo
foo.a = 1
foo.b = 2
function f()
return setmetatable({}, foo)
end
local bar = {}
bar.__index = bar
bar.c = 3
function g()
return setmetatable(f(), bar) --Warns "setmetatable should take a table"
end
local a = f().a --autocompletes without warning
local b = g().b --autocompletes without warning
local b = g().c --doesn't autocomplete, warns "Type '{ @metatable foo, {| |} }' does not have key 'c'"
I really appreciate your response 9 months later. As you’ve said, asserting the type as “any”, or “{any}” would definitely be a suitable workaround, easily replaceable once the Type Checker no longer has this issue. Thanks for your help.