OOP Type Checking Problems

Hey,
I’m running into issues trying to type check an OO system I’ve made. I’m new to type checking with typeof(setmetatable(...)) and simply don’t understand what the problem is. I’d appreciate whatever correction anyone can give.

Simplified Code

ModuleA:

--!strict

type ModuleAType = {
	__index: ModuleAType;
	Foo: (self: TypeA) -> ();
	new: (data: {[string]: string}) -> (TypeA);
}

local ModuleA: ModuleAType = {} :: ModuleAType
ModuleA.__index = ModuleA

export type TypeA = typeof(setmetatable({} :: {
	A: string;
	B: string;
}, {} :: ModuleAType))

function ModuleA:Foo()
	print("Foo")
end

function ModuleA.new(data: {[string]: string}): (TypeA)
	local self = {
		A = data.A;
		B = data.B;
	}
	
	setmetatable(self, ModuleA)
	return self
end

return ModuleA

ModuleB:

--!nonstrict

local ModuleA = require(script.Parent.ModuleA)

type ModuleBType = typeof(setmetatable({} :: {
	__index: ModuleBType;
	Bar: (self: TypeB) -> ();
	new: (data: {[string]: string}) -> (TypeB);
}, ModuleA))

export type TypeB = ModuleA.TypeA & typeof(setmetatable({} :: {
	C: string;
}, {} :: ModuleBType)) -- Type Error: Cannot cast '{ }' into 'ModuleBType' because the types are unrelated

local ModuleB: ModuleBType = {} :: ModuleBType -- Type Error: Cannot cast '{ }' into 'ModuleBType' because the types are unrelated
ModuleB.__index = ModuleB
setmetatable(ModuleB, ModuleA) -- setmetatable should take a table

function ModuleB:Bar(): ()
	print("Bar")
end

function ModuleB.new(data: {[string]: string}): (TypeB)
	local self = ModuleA.new(data)
	self.C = data.C
	
	setmetatable(self, ModuleB)
	return self
end

return ModuleB

There are three comments in ModuleB where I’m receiving type errors (lines 13, 15, and 17). When changing the typeof(setmetatable({...}, {} :: ModuleA)) on lines 5-9 of ModuleB to just the {...}, the errors disappear. I’m not sure if that’s the solution; in my mind the engine would then think that I’m not setting the metatable.

I know I’m explaining this terribly, so let me know if you need me to elaborate on anything.

1 Like

Whats the error

Look at lines 13, 15, and 17 of ModuleB.

Instead of creating types ModuleAType and ModuleBType, you can get the type by using typeof(ModuleA).

So, with that you could reformat your classes to look like this, which should work.

-- ModuleA.luau
--!strict

local ModuleA = {}
ModuleA.__index = ModuleA

export type self = typeof(
	setmetatable(
		{} :: {
			A: string,
			B: string
		},
		{} :: typeof(ModuleA)
	)
)

function ModuleA.new(Data: {[string]: string}): self
	local self = setmetatable({}, ModuleA)
	
	self.A = Data.A
	self.B = Data.B
	
	return self
end

function ModuleA.Foo(self: self)
	print(self.A, self.B)
end

return ModuleA
-- ModuleB.luau
--!strict

local ModuleA = require(script.Parent.ModuleA)

local ModuleB = {}
ModuleB.__index = ModuleB

export type self = ModuleA.self & typeof(
	setmetatable(
		{} :: {
			C: string
		},
		{} :: typeof(ModuleB)
	)
)

setmetatable(ModuleB, ModuleA)

function ModuleB.new(Data: {[string]: string}): self
	local self = setmetatable(
		ModuleA.new(Data) :: self,
		ModuleB
	)
	
	self.C = Data.C
	
	return self
end

function ModuleB.Bar(self: self)
	print(self.C)
end

return ModuleB
1 Like

There are many different ways to approach this kind of typechecking. Currently, my method is doing the following:

type Class = {
    PropertyFoo: string,
    PropertyBar: number,

    FooBarMethod: (self: Class, printThis: string) -> (number),
}

local Class = {}
Class.__index = Class
function Class.new()
    local self = (setmetatable({
        PropertyFoo = "Foo",
        PropertyBar = 123,
    }, Class):: unknown):: Class --> slightly hacky, but works

    return self
end
function Class.FooBarMethod(self: Class, printThis: string)
    print(printThis)
    return 1
end

-->> (results)
local NewClass = Class.new()

local thisIsANumber = NewClass:FooBarMethod("hello!")
print(thisIsANumber + 5) --> will not warn, prints 6

NewClass.PropertyFoo = "foooooo" --> this is okay
NewClass.PropertyFoo = 123 --> this is not okay
--<< (results)
1 Like

@index_self @VegetationBush
Thank you both! I appreciate both of your replies, but I’ve opted for the first as it just seems more clean and intuitive to me.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.