So, i have decided to use type checking with my OOP structure and i run into a problem where i have to make modules require each other to get their exported types.
I use custom Player and Team objects with their types written based on this tutorial:
Player object has a ‘Team’ property that uses the custom TeamTable type.
type self = {
Name: string,
Instance: Player,
Character: Model,
Team: Team.TeamTable --RIGHT HERE
}
export type PlayerTable = typeof(setmetatable({} :: self, Player))
And the Team object uses the custom PlayerTable type in it’s functions.
--RIGHT HERE
function Team:CanJoin(Player: Player.PlayerTable, JoinPrivilage: boolean?) : boolean
Unfortunately this leads to the fact that these modules need to require each other which creates the cyclic module dependency.
Team module
Player module
I know how to let modules require each other since i’ve done it before.
Basicaly this:
This solution will not let me get the types, as shown here:
It also gets rid of any autocomplietion that i prepared in the module with type checking.
Does anybody know what the solution could be? How could i store these types differently?
So you know, this is intended behaviour because types are done statically, this behaviour won’t be changed.
You can get around the warning by casting your require call to any (local module = require(path.to.module) :: any but you lose type checking.
If you need types, you could define and import types in a third module as mentioned above this post
-- Types module
export type TeamTable = {
-- ...
}
export type self = {
Name: string;
Instance: Player;
Character: Model;
Team: TeamTable
}
export type Player = { -- assuming this is your class methods table
SetTeam: (self: PlayerTable, team: TeamTable) -> ();
SomeOtherMethod: (self: PlayerTable, number) -> (boolean, number);
__index: Player; -- important to inherit methods from the Player type
}
export type PlayerTable = typeof(setmetatable({} :: self, {} :: Player))
-- return a number to allow this module to be considered valid
-- tester:
local function a(player: PlayerTable)
player:SetTeam({} :: TeamTable) -- ok
player.Name = 'bob'
player:ThisMethodDoesntExist({} :: TeamTable) -- not ok
player.Instance = Instance.new('Fire') -- not ok
end
return 1 -- return 1 to allow this module to be considered valid
Then in your script,
local types = require(script.Types)
local function doSomethingWithAPlayer(player: types.PlayerTable)
player:SetTeam({} :: types.TeamTable) -- again, ok
player.Name = 'bob' -- ok
player:nonexistentMethod() -- not ok
player.Character = Instance.new('Fire') -- not ok
end
Cyclic module dependency is valid here, because these modules are literaly requiring each other which causes the infinite loop.
As for the solution, creating a special module with copied types will only generate more unnecessary work, since i will have to update these types when i edit the modules.
Combining the modules is also not plausible since they are used individualy by other modules.
The real problem here is the inability to get types without requiring modules, since the Team module uses the Player module only for easier autocomplietion from its type.
I’ve made a temporary, empty replacement type in the Player module for the team property and i will only lose autocomplietion in minor scenarios.
The real solution for this problem would be to add GLOBAL TYPES which can be accessed anywhere without needing to require modules, example:
global type PlayerTable = typeof(setmetatable({} :: self, Player))
--BOOM, NOW I CAN EASLY USE MY PLAYER TYPE ANYWHERE WITHOUT MODULE REQUIRING NIGHTMARE
I would make a feature request, but im unable to post any topics there.
You can make an issue on the Luau github detailing your usecases and why the addition is necessary. Typically in those cases you get a much faster response since it goes directly to the Luau team instead of having to go through devforum and having the staff here pass the message along to the relevant team