Type-checking error when accessing member of metatable via self

I have a simple modulescript for an inventory system which I intiailize like this:

function Inventory.new()
	local i=setmetatable({},Inventory)
	i.Storage={} :: {[string]:Item}
	i.Gui = script.InventoryGUI :: ScreenGui 
	i.ContextMenu=nil
	i.Inspecting=nil
	i.UsingItem=nil
	--There's some limitations to using the normal mouse class (specifically, how you can't set multiple target filters). I use a custom module by crazyman32 to get around this.
	i.Mouse=require(script.Mouse).new()
	i.MaxItems=7
	--Store all button connections so we can easily disconnect them later when the item is removed from inventory
	i.Connections={} :: {[string]:RBXScriptSignal}
	i.InventoryCount=0 :: number
	
	
	return i
end

However, if I create a method and attempt to access a member like i.Gui and modify it, I will usually get a type-checking error

function Inventory:ClickedItem(clicked: Item)
	--Shows context menu
	local contextMenu=script.ContextMenu:Clone()
	contextMenu.Parent=self.Gui
	self.ContextMenu=contextMenu
	
	contextMenu.InspectButton.MouseButton1Click:Connect(function()
		--ToDo: Inspect object
	end)	
	contextMenu.UseButton.MouseButton1Click:Connect(function()
		self.UsingItem = clicked
		self.Gui.Enabled=false -- Returns a type-checking error stating that self.Gui is of type Instance instead of ScreenGUI
		local label=Instance.new("ImageLabel")
		label.Image=clicked.Icon
		label.Size=UDim2.new(0,40,0,40)
		self.Mouse:AttachGUI(label)
	end)
end

When I attempt to disable i.Gui via accessing it by self, it appears that the type-checker is no longer aware that i.Gui is a ScreenGUI. This is pretty minor and seems to be limited to the strict typechecking mod,e but I want to make sure:

Is this a limitation with the type-checking system or am I doing something incorrect?

1 Like

This isn’t a bug in your logic, it’s just how Luau’s type checker works. By default it only sees script.InventoryGUI as an Instance, so later it doesn’t know it’s a ScreenGui. The fix is to define an explicit type for your Inventory class (e.g. Gui: ScreenGui) so the checker can keep track of the right type.

2 Likes

Is that not what I’m already doing here?

i.Gui = script.InventoryGUI :: ScreenGui 
2 Likes

You’re right — the :: ScreenGui cast on assignment does help in that line, but Luau doesn’t remember it for the whole object unless you define a type for the table itself. Right now, i is just {} with no defined shape, so all of its fields default to any/Instance.

That’s why you need to declare a type for your Inventory table, like this:

type Inventory = {
    Gui: ScreenGui,
    -- other members...
}

local Inventory = {}
Inventory.__index = Inventory

function Inventory.new(): Inventory
    local i = setmetatable({}, Inventory) :: Inventory
    i.Gui = script.InventoryGUI
    return i
end

With this, the checker will know permanently that self.Gui is a ScreenGui, not just in the line where you assign it.

2 Likes

Luau can’t infer the type for self inside a method like that, at least not in the way you’d want. The relationship between the object’s table type and the method’s self through a metatable is unclear and simply undecidable.

You’d either have to manually annotate self (like with self: typeof(Inventory.new(nil :: any))), or find some other ‘OOP idiom’ that works good enough.

My advice: just stick with modelling data structures with simple tables and functions if you care about good type-checking. It’s cleaner, faster and has better support than any metatable magic.

4 Likes

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