Guide to Type-Checking with OOP

I omitted it for simplicity of the example but it’d look something like this if I wrote them:

type PrivateVariables = {
    name: string -- yippee! type safety 
}

loca privateVariables: { [Something]: PrivateVariables } = {}

local Something = {}
Something.__index = Something

function Something.new(): Something
     local self = setmetatable({}, Something)

    self.nothing = nil

    privateVariables[self] = {
        name = name,
    } -- tadaaaa
    
    return self
end

type Something = typeof(Something.new(…)) -- right under new, before methods

function Something.doSomething(self: Something): ()
    privateVariables[self].name = 2 -- type warning! i think… i’m on mobile so i can’t test it
end

return Something
5 Likes

nice tutorial. Is there a way to access created types from modules? I use module.Type and it sort of works but I can access things like .__index

Adding export before a type makes it accessible under the namespace the module gets required by.

-- Module.luau
export type MyType = number
return nil

-- Server.luau
local MyModule = require(Module.luau)
local A: MyModule.MyType = 1

Unfortunately having .__index exposed is a side effect of capturing the whole class table as a metatable.

However, you can set Car.__index to just a new table and then put the methods inside it. It wastes more memory, but it fixes your problem.

local Car = {}
Car.__index = {}

-- Same Stuff...

function Car.__index.Boost(self: Car): ()
	self.Speed += 50
end
1 Like

guess I gotta cope lol. anyway thanks

I just predefined the entire module script as a type in a separate module underneath the module script and require it for typing.

So tedious but looks cleaner imo, and let’s u really think ab what ur coding.

Pre defining your object data table and the object module

I found other way to do this (but non index)

local hi = {}

function hi.new()
	local b = {}
	b.Sick = 1
	b.run = "hi"
	function b:Rap()
		print("RAPPIN'")
	end
	
	function b.No()
		print("uhh")
	end
	
	return b
end

export type raiter = typeof(hi.new(...))
return hi

And also with double types:

local system = {}

function system.new()
	local base = {}
	
	local private = {}
	
	function base.newEnumerator()
		local h = {}
		h.NNn = 1
		return h
	end
	
	export type EnumerateCall = typeof(base.newEnumerator())
	
	return base
end

export type SystemCall = typeof(system.new(...))

return system
1 Like

The main problem with metatable OOP is a typechecking…
Its nearly impossible to be implemented properly here so consider using pointer OOP

This article misses the fact that you must also include the type definitions for each function in self

i.e.

type self = {
	Speed: number,
}

type methodDefs = {
    Boost:(self: DerivedClass)->nil
}

export type DerivedClass= typeof(setmetatable({}, DerivedClass) & methodDefs & super.super

Note, the method definitions cannot be inside the self type or the Linter will complain.
The class at the very top of the inheritance hierarchy also cannot have the method definition type or the Linter will also complain

This reminds me of C++ header files :cry:

Doing that is a bad idea btw
Becouse typeof() exists
and also typeof(setmetatable({}, DerivedClass) can be turned into: setmetatable<{},DerivedClass>

Also in luau empty tuples “()” represents void/null while nil is a data type by itself.

That is true, but function types are of the form ( ...: any? ) -> return_type, and -> nil is required for a function type if it dose not return anything.

I also have not had any instance where the type decomposed into a ‘setmetatable<{}, DerivedClasss>’

I get issues if I use typeof(DerivedClass.new()), especially with inheritence

What would you suggest as my game currently uses a metatable architecture?

No.
You can grab function type via typeof() aswell + it will grab function description aswell.

Metatable OOP does not suit use in inheritance, honestly, and you should consider switching to other kinds of OOP, like C-like static OOP or closure OOP, not just in this case but in general because metatable OOP is heavily outdated and runs not as well in Luau as other types of OOP.
Also typeof(DerivedClass.new()) is not really a good idea to do.
You should make a strict type for that instead.
setmetatable<> allows doing that thanks to the new type solver.

Wouldn’t this approach store the methods inside of the object table rather than the class?

So unless you are planning on using it as a singleton, you would be wasting a lot of memory due to duplicate functions.