Reproduction Steps
Note: The cause of this issue may be similar to that of a previous thread I wrote (see High-complexity modules prevent the type checker from operating properly). I cannot seem to create a simple reproduction (even when deriving the previous thread’s script generator), so I will need to allow engineers to work with a private copy of the place and script I noticed this issue in. A private message has been attached and given to the bug reports group, but if any of you need access to it in order to assist, I will add you to the private message manually.
This bug requires the Luau-Powered Autocomplete & Language Features beta to be enabled.
When I write modules, I tend to define two types:
local Module = {}
Module.__index = Module
function Module:Method()
end
function Module.new()
return setmetatable({Key = 123}, Module)
end
-- The first type captures the values within an instance of the module
type ModuleInstance = {
Key: number
}
-- The second type captures the module itself:
type ModuleStatic = typeof(Module)
-- And then this type is used in any methods (self is casted to this type in a specific way, see below):
type Module = ModuleStatic & ModuleInstance
When this bug occurs, referencing Module
(type) will treat it as if it is only one of the two types it is joining, such as only being ModuleInstance
. It will completely omit ModuleStatic
from the union. I cannot guarantee if it always favors this, I speculate it does only because ModuleInstance
's equivalent in my module is far, far more simple.
Expected Behavior
For obvious reasons, I expect the union type to behave like a union rather than just one of the two types.
Actual Behavior
For classes that export large and complicated APIs, the equivalent of the Module
type in the code block above will behave as if it is only one of the two types, in my case, ModuleInstance
. It completely omits the existence of ModuleStatic
from the union.
A YouTube video showing this behavior can be seen here: 2022 01 26 04 45 06 - YouTube
As is visible in the video, this module is extremely large and, unfortunately, fragmenting it is not an option because all members of this module pertain to one specific object type. Splitting it apart is simply not an option, because there are no parts to split in the first place.
Workaround
The current workaround is to manually declare usages of the type as a union rather than as a type referencing the union (that is, rather than using Module
, I have to enter ModuleStatic & ModuleInstance
explicitly).
Additionally, albeit slightly unrelated to the topic, I have found that my method of casting self
(as seen in the YT video) helps to prevent issues from an overcomplicated API like the original thread I linked in the introduction. That is,
local self: Module = self::any
is less prone to causing type checker issues than
local self = self::Module
As far as why this is true, I have no idea, I speculate it has to do with casting to any causing the type checker to not try to check compatibility between the automatic type of self
vs Module
.
Issue Area: Studio
Issue Type: Other
Impact: Moderate
Frequency: Often
A private message is associated with this bug report