Annotating a nullable generic variadic as a return of a function

Has anyone found a way to do something similar to this?
type A<T...> = () -> (T...?)

I want a function that can either return T... or nil

it doesn’t work and I’ve also tried many ways it still doesn’t work for examples
type A<T...> = () -> T...?
type A<T...> = () -> T... | nil
type A<T...> = () -> T... & nil
type A<T...> = () -> (T... | nil)
type A<T...> = () -> (T... & nil)
type A<T...> = () -> ((T...)?)
type A<T...> = () -> ((T...) | nil)
type A<T...> = () -> ((T...) & nil)
type A<T...> = () -> (T...) | () -> ()
type A<T...> = () -> () & () -> (T...)

Now, the below line doesn’t produce errors or warnings but it’s still not working as intended after I checked the typing properly:
type A<T...> = () -> (T...) & () -> ()
This one always return T... never nil

type A<T...> = () -> (T...)

then when you want to annotate a function so that it can also return nil:

module.test = function()
	
	if math.random() > 0.5 then
		return 1,2,3,4,5
	else
		return nil
	end
	
end :: A<number?>
1 Like

You cannot make a union with a generic type-pack. The reasoning behind this is quite simple: a type-pack can have an arbitrary number of types (even zero), but a union must always be of a single type.

Considering the following case. What would A and B even be here?

type T<Pack...> = Pack... | nil

type A = T<string, boolean>
type B = T<>
1 Like

Actually the problem is from my own signal module

Connect: (self: Signal<T...>, TCallback<T...>) -> (Connection?),
Wait: (self: Signal<T...>) -> (T...) & (self: Signal<T...>) -> (),

I declared <T…> type pack so that it type checks methods like connect and wait
This is signal is a bit special because if the signal is dead or destroyed, it will disconnect every connections and wait threads are resumed with nil
So Wait can return as either T… or nil
For example

local s: Signal<string, number>  = Signal.new()
local a, b = s:Wait()

I want the a and b variable type to be string? and number? not string and number
I know that I can just declare them manually like this
a: string?, b: number?
But I want the type to automatically know that from my signal type not by manually declaring, becuase if I don’t annotate the type manually it sees a and b as string and number
I’ve tried
Wait: (self: Signal<T...>) -> (T...)?
But it thinks Wait is either a function or null rather than its return type
Is there a way to annotate function like this? or is it still not implemented

Oh, I see what you’re doing!

Yeah, this seems like an interesting edge-case. I think what’s really happening here is that the solver (both old and new) will always infer for the first signature in the overload because both have the same parameters.

A symptom of this behavior is that flipping the order of the signatures literally changes the result.

local NumberFirst = (nil :: any) :: (() -> number) & (() -> string)
local StringFirst = (nil :: any) :: (() -> string) & (() -> number)

-- A: number, B: string
local A, B = NumberFirst(), StringFirst()

In both cases, what you’d really want would be both returning number | string, but the types don’t normalize to that for some reason. This might just be a necessary evil due to semantic subtyping.

Regardless, if this could even be ‘fixed’, you’d still run into the problem of trying to union a type-pack. This isn’t orthogonal to your use case–even the built-in type definitions run into this restriction–so a resolution should hopefully show up some day.

1 Like
-- OptionResult.lua
export type Success<T...> = { success: true, value: { T... } }
export type Failure = { success: false }

export type Option<T...> = Success<T...> | Failure

local OptionResult = {}

function OptionResult.Some<T...>(...: T...): Success<T...>
	return { success = true, value = { ... } }
end

function OptionResult.None(): Failure
	return { success = false }
end

return OptionResult
1 Like