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?
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.
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.
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.