I have a module, called Class++, which has a complex type system within itself. It is designed for the New Type Solver and works perfectly well in both Strict and Non-Strict modes on its own. However, when used with another module, such as Signal+, it causes an important problem.
Class++ has a built-in system where it uses a type alias and a type function internally, which grabs the type information from the provided classData, and uses it to construct a class type. Like I mentioned above, this works perfectly well on its own, but the moment that Class++ is provided a type within this classData table that is too complex, the whole Roblox Studio process crashes.
You can reproduce this issue by simply inserting Class++ and Signal+ in a workspace, using the New Type Solver, and by simply entering this code in a Script:
local ClassPP = require(script.Parent.ClassPP)
local Signal = require(script.Parent.Signal)
local class = ClassPP.class
local NSignal = Signal()
local testClass = class "Test" {
Public = {
Changed = NSignal
}
}
Upon pasting/writing this code, Studio hangs immediately and crashes very soon after. I’m not sure why this happens, but I do have some theories and additional bits of information that could help in pinpointing the root of the issue.
Like I mentioned above, Class++ uses an overloaded function that provides arguments to a specific type function in order to create the necessary types. Normally, this should work perfectly fine, but when it has been provided with a complex type, such as a generic type like Signal:
export type Connection = {
Connected: boolean,
Disconnect: typeof(
-- Removes the connection from the signal.
-- The connection’s data remains.
function(connection: Connection) end
)
}
export type Signal<Parameters...> = {
Connect: typeof(
-- Connects a function.
function(signal: Signal<Parameters...>, callback: (Parameters...) -> ()): Connection end
),
Once: typeof(
-- Connects a function, then auto-disconnects after the first call.
function(signal: Signal<Parameters...>, callback: (Parameters...) -> ()): Connection end
),
Wait: typeof(
-- Yields the calling thread until the next fire.
function(signal: Signal<Parameters...>): Parameters... end
),
Fire: typeof(
--✨
function(signal: Signal<Parameters...>, ...: Parameters...) end
),
DisconnectAll: typeof(
-- Erases all connections.
-- <em>Much faster than calling <code>Disconnect</code> on each.</em>
function(signal: Signal<Parameters...>) end
),
Destroy: typeof(
-- Erases all connections and methods.
-- <strong>To fully erase, also remove all references to the signal.</strong>
function(signal: Signal<Parameters...>) end
)
}
It causes a crash. However, by digging and changing things around quite a bit, I have managed to find an interesting detail:
export type Connection = {
Connected: boolean,
Disconnect: typeof(
-- Removes the connection from the signal.
-- The connection’s data remains.
function(connection: Connection) end
)
}
export type Signal<Parameters...> = {
Connect: typeof(
-- Connects a function.
function(signal: Signal<Parameters...>, callback: (Parameters...) -> ()): Connection end
),
Once: typeof(
-- Connects a function, then auto-disconnects after the first call.
function(signal: Signal<Parameters...>, callback: (Parameters...) -> ()): Connection end
),
Wait: typeof(
-- Yields the calling thread until the next fire.
function(signal: Signal<Parameters...>): any end -- Changed from Parameters... to any
),
Fire: typeof(
--✨
function(signal: Signal<Parameters...>, ...: any) end -- Changed from Parameters... to any
),
DisconnectAll: typeof(
-- Erases all connections.
-- <em>Much faster than calling <code>Disconnect</code> on each.</em>
function(signal: Signal<Parameters...>) end
),
Destroy: typeof(
-- Erases all connections and methods.
-- <strong>To fully erase, also remove all references to the signal.</strong>
function(signal: Signal<Parameters...>) end
)
}
This works perfectly fine. It causes no crashes, nor any problems. By changing the Wait and Fire types to no longer use the Parameters generic, the problem gets solved.
Here’s a simple place-file to reproduce the bug:
BugPlace.rbxl (81.0 KB)
Expected behavior
I expect the type solver to be able to infer the types properly without causing a major crash such as this.
A private message is associated with this bug report