Hello!
I’m trying to write some code to help me define classes quicker, and I was hoping for the created classes to have typechecking support.
--!strict
local Class = {}
--MN: MethodName
--M : Method
--PN: ParamName
--P : Param
type _Prototype<MN, M, PN, P> = {
__index: _Prototype<MN, M, PN, P>,
className: string,
new: ({ [PN]: P }) -> _ClassType<MN, M, PN, P>,
IsA: (_ClassType<MN, M, PN, P>, string) -> boolean,
[MN]: M,
}
type _ConstructorPattern<PN, P> = { [PN]: P }
type _ClassType<MN, M, PN, P> = typeof(setmetatable({} :: _ConstructorPattern<PN, P>, {} :: _Prototype<MN, M, PN, P>))
function Class.new<MN, M, PN, P>(className: string, impl: { [MN]: M }, desc: { [PN]: P })
local newClass = {} :: _Prototype<MN, M, PN, P>
newClass.__index = newClass
newClass.className = className
function newClass.new(descriptor: _ConstructorPattern<PN, P>): _ClassType<MN, M, PN, P>
local self = setmetatable({} :: _ConstructorPattern<PN, P>, newClass)
for paramName, value in pairs(descriptor) do
self[paramName] = value
end
return self
end
function newClass:IsA(className: string): boolean
return newClass.className == className
end
for methodName, method in pairs(impl) do
newClass[methodName] = method
end
return newClass
end
However, the new classes don’t have any of the typechecking that I would expect to see based on the generics:
type desc = {
myBool: boolean,
}
local impl = {}
function impl:test()
return self.myBool
end
local myClass = Class.new("MyClass", impl, {} :: desc)
local myClassInstance = myClass.new({ myBool = false })
-- doesn't check that all fields of the table are present
print(myClassInstance.myBool)
-- doesn't autofill myBool
myClassInstance:test()
-- doesn't autofill test
Is there something I’m doing wrong? Or is this just a limitation of the typechecker?