Union returns with type packs

I am trying to make a fairly complex library for handling multiple asynchronous functions/threads, but have come to a very specific problem with typing it.

Essentially I am trying to have a function “all” that takes a variadic of functions and returns the first one to return something. I want it so the return is relative to the passed functions, so obviously I should use type packs, but I’m having trouble getting them to work.

local function all<T...>(...: (...any) -> T...): T...
	-- spawns a thread for each function
	-- and returns the first function to finish's return
end

-- dummy functions
local a: () -> number = nil
local b: () -> string = nil
local c: () -> boolean = nil

-- b & c flag, because they do not
-- share the same return type as a
local foo = all(a, b, c)
-- foo is typed 'number', when it should be 'number | string | boolean'

What should I do to “union” the packs so I get the desired type of number | string | boolean?

I would say just dont worry about it since type annotations dont affect the code, only what warnings the editor shows. If your function is well documented and doesn’t combine too much functionality unnecessarily it will be all ok.

I’m not trying to make the function error-free, I could easily annotate everything with ...any and be done.

To clarify what I am trying to achieve: I want the return types for the entered functions (number, string and boolean in my code) to be unioned, so

type Union<A, B, C> = A | B | C

-- number | string | boolean
local foo: Union<number, string, boolean>

But for infinite type args, specifically for type packs.

But that would not do anything. You can comment your function to indicate it works this way.

Sorry if I misunderstood the question, but does this achieve what you wanted?

--!strict

type UnionType = number & string & boolean

local function all<T...>(...: (...any) -> T...): T...
	-- spawns a thread for each function
	-- and returns the first function to finish's return
end

-- dummy functions
local a: () -> UnionType = nil
local b: () -> UnionType = nil
local c: () -> UnionType = nil

-- b & c flag, because they do not
-- share the same return type as a
local foo = all(a, b, c)
-- foo is typed 'number', when it should be 'number | string | boolean'

I’m not the best with types.

cc: @azqjanna

Maybe I should rephrase what I am trying to achieve.

The function all takes in multiple functions with multiple return types (It could be anything, a specific table, a type of userdata, a tuple…) and its return type would be a union of all of those.

With type packs, I am able to get atleast one of the return types for the function, so

local function foo<T...>(func: () -> T...): T...
	return func()
end

local function bar()
	return 1, true, {nil}
end

-- return type is 'number, boolean and {nil}'
local a, b, c = foo(bar)

(Analysis already applies packs to the function without annotation, but this is just a representation of how that would type)

But now if I had it so two functions with two potentially different returns, there becomes a problem, namely I cannot just union two variadic type packs:

local function bar() --> number
	return 1
end

local function baz() --> boolean
	return true
end

-- A... | B... is not valid syntax
-- How would I union the type pack?
local function foo<A..., B...>(func1: () -> A..., func2: () -> B...): A... | B...
	if math.random(1, 2) == 1 then
		return func1()
	else
		return func2()
	end
end

-- I want value to infer as 'number | boolean'
local value = foo(bar, baz)

In my case with all, there could hundreds of functions passed through, so the solution would also have to account for annotating variadics with completely different return types. Does that make sense?

1 Like