Now that #26063 is merged (which is great BTW), I was toying around with an alternate solution to https://github.com/Microsoft/TypeScript/issues/1360 or https://github.com/Microsoft/TypeScript/issues/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:** ```ts // Helper type to drop the first item in a tuple, i.e. reduce its size by 1 type Drop1<T extends any[]> = ((...args: T) => void) extends ((a1: any, ...rest: infer R) => void) ? R : never; type TakeLast< T extends any[], // Create a tuple which is 1 item shorter than T and determine its length L1 extends number = Drop1<T>["length"], // use that length to access the last index of T > = T[L1]; // Example: type Foo = 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: ```ts type MapTuples<T1 extends any[], T2 extends any[]> = { [K in keyof T1]: K extends keyof T2 ? T1[K] : never }; type Bar = MapTuples<[1, 2, 3], [4, 5]>; // [1, 2, never] type Baz = MapTuples<[1, 2], [3, 4, 5]>; // [1, 2] ``` 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 ```ts type DropLast< T extends any[], MinusOne extends any[] = Drop1<T>, > = MapTuples<T, MinusOne>; // DropLast<[1, 2, 3]> is [1, 2, never] // Returns the params of a function as a tuple type Params<F extends (...args: any[]) => void> = F extends ((...args: infer TFArgs) => any) ? TFArgs : never; // creates a function type with one less argument than the given one type DropLastArg< F extends (...args: any[]) => void, FArgs extends any[] = Params<F>, RArgs extends any[] = DropLast<FArgs> // ** ERROR ** > = (...args: RArgs) => void; ``` the last argument is still present, but now has type `never` ```ts type F1 = 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 ```ts type ForceTuple<T> = T extends any[] ? T : any[]; type DropLastArg< F extends (...args: any[]) => void, FArgs extends any[] = Params<F>, RArgs extends any[] = 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: ```ts // notice how we now Map from T1 to T2 type MapTuples<T1 extends any[], T2 extends any[]> = { [K in keyof T1]: K extends keyof T2 ? T2[K] : never }; // MapTuples<[1, 2], [4, 5, 6]> is [4, 5] type DropLast< T extends any[], // create a tuple that is 1 shorter than T MinusOne extends any[] = 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] :) type F1 = 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: ```ts type Drop1<T extends any[]> = ((...args: T) => void) extends ((a1: any, ...rest: infer R) => void) ? R : never; type TakeLast< T extends any[], // Create a tuple which is 1 item shorter than T and determine its length L1 extends number = Drop1<T>["length"], // use that length to access the last index of T > = T[L1]; type MapTuples<T1 extends any[], T2 extends any[]> = { [K in keyof T1]: K extends keyof T2 ? T2[K] : never }; type DropLast< T extends any[], // create a tuple that is 1 shorter than T MinusOne extends any[] = Drop1<T>, // and keep only the entries with a corresponding index in T > = MapTuples<MinusOne, T>; type Params<F extends (...args: any[]) => void> = F extends ((...args: infer TFArgs) => any) ? TFArgs : never; type ForceTuple<T> = T extends any[] ? T : any[]; type ForceFunction<T> = T extends ((...args: any[]) => any) ? T : ((...args: any[]) => any); type Promisify< F extends (...args: any[]) => void, // Extract the argument types FArgs extends any[] = Params<F>, // Infer the arguments for the promisified version PromiseArgs extends any[] = ForceTuple<DropLast<FArgs>>, // Parse the callback args CallbackArgs extends any[] = Params<ForceFunction<TakeLast<FArgs>>>, CallbackLength = LengthOf<CallbackArgs>, TError = CallbackArgs[0], // And extract the return value TResult = 1 extends CallbackLength ? void : CallbackArgs[1] > = (...args: PromiseArgs) => Promise<TResult>; ``` ## Examples ```ts type F1 = (arg1: number, arg2: string, c: (err: Error, ret: boolean) => void) => void; type F1Async = Promisify<F1>; // F1Async is (arg2: number, c: string) => Promise<boolean>; (YAY!) ``` ## Checklist My suggestion meets these guidelines: * [x] This wouldn't be a breaking change in existing TypeScript / JavaScript code * [x] This wouldn't change the runtime behavior of existing JavaScript code * [x] This could be implemented without emitting different JS based on the types of the expressions * [x] This isn't a runtime feature (e.g. new expression-level syntax)