Typechecking in Luau is incredibly powerful to communicate to yourself and other developers as to the relative intent of user data, which allows for faster development. Additionally, typechecking, when done correctly, can allow for powerful autofill.
Object-oriented programming in Lua is a strange concept, since it’s an abstract approach to a programming language not otherwise built for OOP. However, the generally accepted method of creating classes looks something like this:
local TestClass = {}
TestClass.__index = TestClass
function TestClass.new(Value1, Value2) -- Constructor
local self = setmetatable({}, TestClass)
self.Name = Value1
self.Age = Value2
self.PrivateThing = "buh"
return self
end
function TestClass:DoThing(myNumber)
return {
foo = 5 + myNumber + self.Age
}
end
return TestClass
By default, there is autofill only in some cases, and additionally, there is no typechecking for property and parameter clarification.
A strong typechecking start is by creating the class type, which looks like this:
type Class = typeof(setmetatable({}, TestClass))
This is scalable, meaning you can add property typechecking as well:
type Class = typeof(setmetatable({}, TestClass)) & {
Name : string,
Age : number,
}
From here, you have a great start to type checking and autofill, with a type that can be exported:
--!strict
local TestClass = {}
TestClass.__index = TestClass
type Class = typeof(setmetatable({}, TestClass)) & {
Name : string,
Age : number,
}
function TestClass.new(Value1 : string, Value2 : number) : Class
local self = setmetatable({}, TestClass)
self.Name = Value1
self.Age = Value2
self.PrivateThing = "buh"
return self
end
function TestClass:DoThing(myNumber : number) : {foo : number}
return {
foo = 5 + myNumber + self.Age
}
end
return TestClass
However, there are a couple of issues. For example, .__index
is seen as a usable class value, private properties appear in autofill, and private properties show linter warnings within the class (screenshots below show these).
So from here, let’s create a module that allows us to typecheck the class and the objects well. The module code should look like this:
-- typeof(metatable) gives the linter information about what methods are in your class.
-- unioning typeof({index = nil :: never}) tells the linter to separate methods and properties.
-- We also set __index in the type definition to a "never" type so that it is clear to the programmer
-- that it should not be used.
-- A combination of all of this only autofills class functions, object functions, and public properties
return function(Class)
local ClassInstance = setmetatable({__index = nil :: never}, Class)
return nil :: typeof(ClassInstance) & typeof({__index = nil :: never})
end
Then you can adjust your class type:
type Class = typeof(require(script.Class)(TestClass)) & {
Name : string,
Age : number,
}
Now, methods and properties are separated in autofill, __index clearly is listed as something not intended to be read, and private properties do not show. In this case, self.PrivateThing
no longer auto-fills outside the example class scope:
That’s all! Good luck with your OOP-typechecking experience.