Background
I'm trying to replace an existing overloaded function with rest parameters using labeled tuple elements.
Original code
This is a simplified version of the original overloaded function:
function doSomething(arg1: string, arg2: number):void;
function doSomething(arg1: string, arg2: number, arg3: unknown[]):void;
function doSomething(arg1: string, arg2: number, arg3: boolean):void;
function doSomething(arg1: string, arg2: number, arg3: unknown[], arg4: boolean):void {
// ...implementation
}
So the third and fourth arguments are optional but when supplied there order can be:
arg3: unknown[]
arg3: unknown[], arg4: boolean
arg3: boolean
Attempted solution
I decided to create the labeled tuple elements first and then depending on the supplied type variable types either set their type to the supplied type or to never
. Then filter that tuple removing any element that's never
and return the result.
type CalcAdditionArgs<ThirdArgType extends unknown[], FourthArgType extends boolean> = [
myArg3: ThirdArgType extends [] ? never : ThirdArgType,
myArg4 : [FourthArgType] extends [boolean] ? FourthArgType extends true ? true : never : never
]
type GetAdditionalArgs<ThirdArgType extends unknown[], FourthArgType extends boolean> =
FilterType<CalcAdditionArgs<ThirdArgType, FourthArgType>, never>
FilterType
is a modified version of the tuple filtering utility found here https://mcmap.net/q/497418/-typescript-remove-entries-from-tuple-type
type FilterType<T extends unknown[], U = undefined> =
(T extends [] ?
[] :
(T extends [infer H, ...infer R] ?
([H] extends [U] ?
FilterType<R, U> :
[H, ...FilterType<R, U>]) :
T
)
);
Just for clarity, this is the function that uses them
function execute<
ThirdArgType extends unknown[] = [],
FourthArgType extends boolean = false
>(
arg1: string,
arg2: number,
...args:GetAdditionalArgs<ThirdArgType, FourthArgType>
): void {
// do something here
}
Problem
This is the output of the 2 utility types:
type a = CalcAdditionArgs<[], false>; // [myArg3: never, myArg4: never]
type b = CalcAdditionArgs<[], true>; // [myArg3: [string, number], myArg4: never]
type c = CalcAdditionArgs<[string, number], false>; // [myArg3: [string, number], myArg4: never]
type d = CalcAdditionArgs<[string, number, Function], true>; // [myArg3: [string, number, Function], myArg4: true]
type e = GetAdditionalArgs<[], false>; // []
type f = GetAdditionalArgs<[], true>; // [true]
type g = GetAdditionalArgs<[string, number], false>; // [string: number]
type h = GetAdditionalArgs<[string, number, Function], true>; // [[string, number, Function], true]
As you can see, GetAdditionalArgs
(or rather FilterType
) is stripping the tuple element labels.
Question
I'm unable to understand how - if it's actually possible - to go about creating and then manipulating a tuple type within a utility type. i.e create an empty tuple and then add the required types to it. Hence my approach to the solution to create the populated tuple up front and then remove elements instead.
- Does anyone know why the tuple element labels are being stripped by
FilterType
and is there a way to fix this using the existing solution?
Or
- Is there a better/simpler solution to achieve the result i'm looking for?
Solution
Thanks to the response from @captain-yossarian and this SO answer from @ford04 (https://mcmap.net/q/2032514/-type-level-operations-for-labelled-tuple) i was able to rethink my approach to the solution using rest parameters:
type Append<E extends [unknown], A extends unknown[]> = [...A, ...E]
type GetMyArrayArg<T extends unknown[]> = [myArrayArg: T extends [] ? never : T]
type GetMyBooleanArg<T extends boolean> = [myBooleanArg : [T] extends [boolean] ? T extends true ? true : never : never]
type AddParameter<T extends [unknown], U extends unknown[] = []> =
T extends [] ?
U :
T extends [infer H] ?
[H] extends [never] ?
U :
Append<T, U> :
U
type GetAdditionalArgs<ThirdArgType extends unknown[], FourthArgType extends boolean> =
AddParameter<
GetMyBooleanArg<FourthArgType>,
AddParameter<
GetMyArrayArg<ThirdArgType>>
>
type a = GetAdditionalArgs<[], false>; // []
type b = GetAdditionalArgs<[], true>; // [myBooleanArg: true]
type c = GetAdditionalArgs<[string, number], false>; // [myArrayArg: [string, number]]
type d = GetAdditionalArgs<[string, number, Function], true>; // [myArrayArg: [string, number, Function], myBooleanArg: true]