Is it possible for type checker to identify a type when you pass the object into the function's parameter or no?

Here is my CafeTest.lua Module:

local Members = require(script.MembersTest)
	
	local Cafe = {}
	Cafe.__index = Cafe
	
	type self = {
		Members: Members.Members,
		["_Members"]: Members.Members
	}
	
	export type Cafe = typeof(setmetatable({} :: self, Cafe))
	
	function Cafe.new(): Cafe
		local self = setmetatable({} :: self, Cafe)
	
		self.Members = Members.new() self._Members = self.Members
		
		return self
	end
	
	function Cafe.GetMembers(self: Cafe): ()
		return (self.Members)
	end
	
	return Cafe

MembersTest.lua Module:

local Member = require(script.MemberTest)
	
	local Members = {}
	Members.__index = Members
	
	type self = {
		Member: Member.Member,
		["_Member"]: Member.Member
	}
	
	export type Members = typeof(setmetatable({} :: self, Members))
	
	function Members.new(): Members
		local self = setmetatable({} :: self, Members)
		self.Member = Member.new() self._Member = Member.new()
		
		return self
	end
	
	function Members.GetMember(self: Members, player): ()
		
	end
	
	return Members	

Member.lua Module:

local Member = {}
	Member.__index = Member
	
	type self = {
		Cafe: Cafe
	}
	
	export type Member = typeof(setmetatable({} :: self, Member))
	
	function Member.new(Cafe): Member
		local self = setmetatable({} :: self, Member)
		
		self.Cafe = Cafe self._Cafe = self.Cafe
		
		return self
	end
	
	function Member.GetMember(self: Member, player): ()
		
	end
	
	function Member.SetRankNum(self: Member, rankNum): ()
		
	end
	
	return Member

In the Member.lua Module, I want it to recognize the Cafe type when I pass it in. How can I do that? Does anybody know???

For reference:

local Member = {}
	Member.__index = Member
	
	type self = {
		Cafe: Cafe
	}
	
	export type Member = typeof(setmetatable({} :: self, Member))
	
	function Member.new(Cafe): Member
		local self = setmetatable({} :: self, Member)
		
		self.Cafe = Cafe self._Cafe = self.Cafe
		
		return self
	end
	
	function Member.GetMember(self: Member, player): ()
		
	end
	
	function Member.SetRankNum(self: Member, rankNum): ()
		
	end
	
	return Member

I am not sure how to do this yet. Any pointers is appreciated. I followed this tutorial:

Of course, this is a dumbed down version. I just wanted to illustrate something. Not something that I will probably use but trying to pass in an object whose type is recognized (it is the Cafe object) is this possible??? Any pointers?

Please place your code blocks into these

"```"
-code here
"```"

(Remove quotes)

Fixed, thanks. Hope it is more readable?

Why dont you do Member.new(Cafe:Cafe)

No, but you can make self.ValueType at home.

I believe it would cause Module circular dependency if I require it like this:

local Cafe = require(script.Parent.Parent.Parent.CafeTest)

local Member = {}
Member.__index = Member

type self = {
	Cafe: Cafe
}

export type Member = typeof(setmetatable({} :: self, Member))

function Member.new(Cafe: Cafe): Member
	local self = setmetatable({} :: self, Member)
	
	self.Cafe = Cafe self._Cafe = self.Cafe
	
	return self
end

function Member.GetMember(self: Member, player): ()
	
end

function Member.SetRankNum(self: Member, rankNum): ()
	
end

return Member

Seems like that would be a problem. Any ideas on how to further refine this?
Or would I just have to drop the IntelliSense feedbacks for Cafe if I do it this way inside a ModuleScript like this??? Thanks.

I am not sure that I follow? Can you please go more into this?

Meaning???

Why Explicit Type Checking Defeats OOP in Luau

This conceptually defeats OOP as a programming paradigm. There should not be any cases where you would want to know of a class type in a function as that is a reflection pattern and is frowned upon for many reasons. Ideally, any case where this is thought to be used can rather be replaced with interfaces which provide a layer of abstraction with a contracted implementation.

The Technical Reality in Luau

Now, the clear and simple way to do this in LUAU is to define a private attribute internal to a class which defines its type using some metadata. For example, you can define a private attribute called __type = "coffee" or whatever feels right. This allows you to identify the type. Because of the dynamic nature of Lua, identifying specific object types is not really possible like it would be in C++ or Java. You need to define this functionality manually using object attributes.

Using Luau’s static typing system, we can actually define proper interfaces and type annotations, but some developers still resort to manual type checking:

-- Type definitions
export type DrinkType = "Coffee" | "Tea" -- union type

-- Anti-pattern: Class with manual type checking
local Coffee = {}
Coffee.__index = Coffee

function Coffee.new(flavor: string): Coffee & {__type: DrinkType, flavor: string}
    local self = setmetatable({}, Coffee)
    self.__type = "Coffee" -- Manual type definition (anti-pattern)
    self.flavor = flavor
    return self
end

-- Anti-pattern: Function with explicit type checking
local function processDrink(drink: {__type: DrinkType})
    if drink.__type == "Coffee" then
        print("Processing coffee with flavor: " .. (drink :: any).flavor)
    elseif drink.__type == "Tea" then
        print("Processing tea with variety: " .. (drink :: any).variety)
    else
        error("Unknown drink type!")
    end
end

However, I highly recommend not using this in practice as OOP is a paradigm where objects that interact don’t need to know what object they are interacting with, rather, they simply need to know how to use the interface and the implemented contracts that are defined within them. This allows clean and decoupled code and ensures sustainability.

The Interface-Based Solution with Luau Static Typing

Luau’s type system actually gives us better tools to implement proper OOP principles without resorting to manual type checking:

-- Define an interface using Luau type system
export type Processable = {
    process: (self: Processable) -> ()
}

-- Base class implementing the interface
local Drink = {}
Drink.__index = Drink

function Drink:process()
    error("Subclasses must implement process()")
end

-- Coffee class with static typing
export type Coffee = Processable & {
    flavor: string,
    new: (flavor: string) -> Coffee,
    process: (self: Coffee) -> ()
}

local Coffee: Coffee = setmetatable({}, {__index = Drink})
Coffee.__index = Coffee

function Coffee.new(flavor: string): Coffee
    local self = setmetatable({}, Coffee)
    self.flavor = flavor
    return self
end

function Coffee:process()
    print("Processing coffee with flavor: " .. self.flavor)
end

-- Tea class with static typing
export type Tea = Processable & {
    variety: string,
    new: (variety: string) -> Tea,
    process: (self: Tea) -> ()
}

local Tea: Tea = setmetatable({}, {__index = Drink})
Tea.__index = Tea

function Tea.new(variety: string): Tea
    local self = setmetatable({}, Tea)
    self.variety = variety
    return self
end

function Tea:process()
    print("Processing tea with variety: " .. self.variety)
end

-- Polymorphic function using the interface
local function processDrink(drink: Processable)
    drink:process() -- No type checking needed!
end

This approach utilizes Luau’s static typing to create a proper interface-based solution. The Processable type acts as an interface that both Coffee and Tea implement. The processDrink function accepts any object that implements this interface, embracing polymorphism without needing to know concrete types.

Why This Matters

When you check types explicitly, you’re coding defensively against the exact flexibility that OOP is designed to provide. Each new drink type would require modifying the processDrink function, creating a maintenance burden that grows with your codebase. With the interface approach, you can add as many drink types as you want without ever touching existing code - they just need to implement the Processable interface.

Luau’s type system actually reinforces good OOP principles by allowing you to express interfaces formally through type definitions. This gives you the best of both worlds - static type checking for early error detection while maintaining the flexibility and decoupling that makes OOP powerful.

Remember that in a well-designed OOP system, components interact through interfaces, not concrete types. Even with Luau’s static typing, you should focus on what objects can do (their methods and behaviors) rather than what they are (their concrete types). This principle leads to more maintainable, extensible, and robust code - especially in large Roblox projects where multiple systems need to interact smoothly.