I really hate to bump this 3 year old thread but this should really be considered (or at least be addressed as to why this isn’t currently possible/feasible).
One suggestion I may add is adding optional generic types (if it’s possible) to such Instances, or creating an alternative generic types (like what Signal uses).
-- Signal implementation
local mySignal: Signal<string> = Signal.new()
mySignal:Fire(1) -- type-check error
mySignal:Connect(function(v: boolean): ()
-- type-check error
end)
-- Proposed implementation
local myRemoteEvent: RemoteEvent<string> = Instance.new("RemoteEvent")
local myRemoteEvent: GenericRemoteEvent<string> = Instance.new("RemoteEvent")
myRemoteEvent:Fire(1) -- type-check error
myRemoteEvent.OnServerEvent:Connect(function(player: Player, v: boolean): ()
-- type-check error
end)
Obviously, it’s not the best solution and inferring the type is probably impossible today, meaning you would have to manually re-define the types, but it’s better than nothing.
Currently, you can do something hacky like the example below, however, it has it’s issues. Although you will see correct autocompletion types, script analysis will not be accurate:
--!strict
-- very lengthy type declarations
export type GenericFireServer<A...> = (self: GenericRemoteEvent<A...>, A...) -> ()
export type GenericFireClient<A...> = (self: GenericRemoteEvent<A...>, Player, A...) -> ()
export type GenericFireAllClients<A...> = (self: GenericRemoteEvent<A...>, A...) -> ()
export type GenericOnServerEvent<A...> = {
Connect: (self: GenericOnServerEvent<A...>, callbackFunction: (Player, A...) -> ()) -> RBXScriptConnection,
ConnectParallel: (self: GenericOnServerEvent<A...>, callbackFunction: (Player, A...) -> ()) -> RBXScriptConnection,
Once: (self: GenericOnServerEvent<A...>, callbackFunction: (Player, A...) -> ()) -> RBXScriptConnection,
Wait: (self: GenericOnServerEvent<A...>) -> (Player, A...)
}
export type GenericOnClientEvent<A...> = {
Connect: (self: GenericOnClientEvent<A...>, callbackFunction: (A...) -> ()) -> RBXScriptConnection,
ConnectParallel: (self: GenericOnClientEvent<A...>, callbackFunction: (A...) -> ()) -> RBXScriptConnection,
Once: (self: GenericOnClientEvent<A...>, callbackFunction: (A...) -> ()) -> RBXScriptConnection,
Wait: (self: GenericOnClientEvent<A...>) -> A...
}
export type GenericRemoteEvent<A...> = {
FireServer: GenericFireServer<A...>,
FireClient: GenericFireClient<A...>,
FireAllClients: GenericFireAllClients<A...>,
OnServerEvent: GenericOnServerEvent<A...>,
OnClientEvent: GenericOnClientEvent<A...>
} & RemoteEvent
-- you would have to typecast the RemoteEvent as any
local myRemoteEvent: GenericRemoteEvent<string> = Instance.new("RemoteEvent") :: any
myRemoteEvent.OnServerEvent:Connect(function(player: Player, message: string): ()
-- because of the way types work, "message" is technically a generic of
-- any (from RemoteEvent) & string (from GenericRemoteEvent<string>).
-- not declaring "message" as a string will give you a generic type.
-- not only that, since "message" is also any (from RemoteEvent),
-- Luau will see nothing wrong if it is defined as any other type
end)
-- same issue as above. this is obviously wrong but will accept it as correct:
myRemoteEvent:FireServer(123)
-- everything table-defined in GenericRemoteEvent<A...> and onwards will also
-- have the same behavior of being a generic of any. this is obviously wrong
-- but will accept it as correct:
local rbxScriptSignal: number = myRemoteEvent.OnServerEvent