You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Now that #26063 is merged (which is great BTW), I was toying around with an alternate solution to #1360 or #25717. The basic idea is to take the parameter list of a NodeJS callback style API as a tuple and generate two other types from it:
1. The type of the last item in the tuple, which can currently be achieved doing this:
// Helper type to drop the first item in a tuple, i.e. reduce its size by 1typeDrop1<Textendsany[]>=((...args: T)=>void)extends((a1: any, ...rest: infer R)=>void) ? R : never;typeTakeLast<Textendsany[],// Create a tuple which is 1 item shorter than T and determine its lengthL1extendsnumber=Drop1<T>["length"],// use that length to access the last index of T>=T[L1];// Example:typeFoo=TakeLast<[1,5,7,8,9,string]>;// string
2. The type of all items in the tuple BUT the last.
This one is tricky and isn't working 100% yet. The basic idea is to work with the reduced tuple again and compare indizes with the original tuple:
As you can see, when T1 is longer than T2, the extra elements get converted to never. In comparison we can use this trick to erase properties from objects, which does not work here. As a result, when we use the following types to try and drop the last argument from a parameter list
typeDropLast<Textendsany[],MinusOneextendsany[]=Drop1<T>,>=MapTuples<T,MinusOne>;// DropLast<[1, 2, 3]> is [1, 2, never]// Returns the params of a function as a tupletypeParams<Fextends(...args: any[])=>void>=Fextends((...args: infer TFArgs)=>any) ? TFArgs : never;// creates a function type with one less argument than the given onetypeDropLastArg<Fextends(...args: any[])=>void,FArgsextendsany[]=Params<F>,RArgsextendsany[]=DropLast<FArgs>// ** ERROR **>=(...args: RArgs)=>void;
the last argument is still present, but now has type never
typeF1=DropLastArg<(arg1: number,arg2: string,arg3: boolean)=>void>;// F1 is (arg1: number, arg2: string, arg3: never) => void
In addition, the mapped tuple type is no longer recognized as a tuple [Symbol.iterator()] is missing in type MapTuples<...>, so we have to force RArgs to be one
typeForceTuple<T>=Textendsany[] ? T : any[];typeDropLastArg<Fextends(...args: any[])=>void,FArgsextendsany[]=Params<F>,RArgsextendsany[]=ForceTuple<DropLast<FArgs>>>=(...args: RArgs)=>void;
but now F1 has type (...args: any[]) => void because we lost the type information.
However with a few changes, we can get closer to the desired result:
// notice how we now Map from T1 to T2typeMapTuples<T1extendsany[],T2extendsany[]>={[KinkeyofT1]: KextendskeyofT2 ? T2[K] : never};// MapTuples<[1, 2], [4, 5, 6]> is [4, 5]typeDropLast<Textendsany[],// create a tuple that is 1 shorter than TMinusOneextendsany[]=Drop1<T>,// and map the entries to the ones at the corresponding indizes in T>=MapTuples<MinusOne,T>;// DropLast<[1, 2, 3]> is [1, 2] :)typeF1=DropLastArg<(arg1: number,arg2: string,arg3: boolean)=>void>;// F1 is (arg2: number, arg3: string) => void
Notice how F1 has the correct argument types, but the names are off by one!
Suggestion
So in conclusion I'd like to see some more improvements to mapped tuples, specifically:
the ability to remove items from them and
complex mapped tuples to still be recognized as tuples.
Use Cases
A BIG usecase is typing NodeJS callback-style APIs, which I came very close to type using this:
typeDrop1<Textendsany[]>=((...args: T)=>void)extends((a1: any, ...rest: infer R)=>void) ? R : never;typeTakeLast<Textendsany[],// Create a tuple which is 1 item shorter than T and determine its lengthL1extendsnumber=Drop1<T>["length"],// use that length to access the last index of T>=T[L1];typeMapTuples<T1extendsany[],T2extendsany[]>={[KinkeyofT1]: KextendskeyofT2 ? T2[K] : never};typeDropLast<Textendsany[],// create a tuple that is 1 shorter than TMinusOneextendsany[]=Drop1<T>,// and keep only the entries with a corresponding index in T>=MapTuples<MinusOne,T>;typeParams<Fextends(...args: any[])=>void>=Fextends((...args: infer TFArgs)=>any) ? TFArgs : never;typeForceTuple<T>=Textendsany[] ? T : any[];typeForceFunction<T>=Textends((...args: any[])=>any) ? T : ((...args: any[])=>any);typePromisify<Fextends(...args: any[])=>void,// Extract the argument typesFArgsextendsany[]=Params<F>,// Infer the arguments for the promisified versionPromiseArgsextendsany[]=ForceTuple<DropLast<FArgs>>,// Parse the callback argsCallbackArgsextendsany[]=Params<ForceFunction<TakeLast<FArgs>>>,CallbackLength=LengthOf<CallbackArgs>,TError=CallbackArgs[0],// And extract the return valueTResult=1extendsCallbackLength ? void : CallbackArgs[1]>=(...args: PromiseArgs)=>Promise<TResult>;
@sirian When I wrote those types last august, they used to work. But since they involve a lot of hacky workarounds, it is very likely that they don't work anymore.
Now that #26063 is merged (which is great BTW), I was toying around with an alternate solution to #1360 or #25717. The basic idea is to take the parameter list of a NodeJS callback style API as a tuple and generate two other types from it:
1. The type of the last item in the tuple, which can currently be achieved doing this:
2. The type of all items in the tuple BUT the last.
This one is tricky and isn't working 100% yet. The basic idea is to work with the reduced tuple again and compare indizes with the original tuple:
As you can see, when
T1
is longer thanT2
, the extra elements get converted tonever
. In comparison we can use this trick to erase properties from objects, which does not work here. As a result, when we use the following types to try and drop the last argument from a parameter listthe last argument is still present, but now has type
never
In addition, the mapped tuple type is no longer recognized as a tuple
[Symbol.iterator()] is missing in type MapTuples<...>
, so we have to force RArgs to be onebut now
F1
has type(...args: any[]) => void
because we lost the type information.However with a few changes, we can get closer to the desired result:
Notice how F1 has the correct argument types, but the names are off by one!
Suggestion
So in conclusion I'd like to see some more improvements to mapped tuples, specifically:
Use Cases
A BIG usecase is typing NodeJS callback-style APIs, which I came very close to type using this:
Examples
Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: