You could make the function generic in T, the element type of the output array (so if the array produces a [X, Y, Z][] then T is [X, Y, Z]) and then give the function a rest parameter whose of a mapped tuple/array type over T:
declare function zipComp<T extends any[]>(
...args: { [I in keyof T]: T[I][] }
): T[]
In the mapped type { [I in keyof T]: T[I][] }, for each numeric-like index I in the T array, the element type T[I] at that index is mapped to an array of those elements T[I][]. So if T is [X, Y, Z], then the mapped type is [X[], Y[], Z[]].
Note that because the mapped type in question is homomorphic (as described in What does "homomorphic mapped type" mean? ) the compiler is able to infer T from the value passed in for args. (If this weren't true, then inference might fail and we'd need to rewrite the call signature.)
Also note that rest argument types tend to be inferred as tuple types by the compiler instead of just unordered array types, which is what we want.
All of this means that, if args is of type [X[], Y[], Z[]] then the output will be of type [X, Y, Z][].
Or at least it should be; let's try it out:
const nb = zipComp([1, 2], [false, true]);
// const nb: [number, boolean][];
const sno = zipComp(["a", "b", "c"], [1, 2, 3], [{}, {}, {}]);
// const sno: [string, number, {}][];
const hmm = zipComp([""], [1], [true], [new Date()], [null],
[undefined], [Symbol()], [() => 2]);
// const hmm: [string, number, boolean, Date, null,
// undefined, symbol, () => 2][]
Looks good! The compiler happily produces the output type from the input type even with relatively long inputs.
Playground link to code
Answer from jcalz on Stack OverflowYou could make the function generic in T, the element type of the output array (so if the array produces a [X, Y, Z][] then T is [X, Y, Z]) and then give the function a rest parameter whose of a mapped tuple/array type over T:
declare function zipComp<T extends any[]>(
...args: { [I in keyof T]: T[I][] }
): T[]
In the mapped type { [I in keyof T]: T[I][] }, for each numeric-like index I in the T array, the element type T[I] at that index is mapped to an array of those elements T[I][]. So if T is [X, Y, Z], then the mapped type is [X[], Y[], Z[]].
Note that because the mapped type in question is homomorphic (as described in What does "homomorphic mapped type" mean? ) the compiler is able to infer T from the value passed in for args. (If this weren't true, then inference might fail and we'd need to rewrite the call signature.)
Also note that rest argument types tend to be inferred as tuple types by the compiler instead of just unordered array types, which is what we want.
All of this means that, if args is of type [X[], Y[], Z[]] then the output will be of type [X, Y, Z][].
Or at least it should be; let's try it out:
const nb = zipComp([1, 2], [false, true]);
// const nb: [number, boolean][];
const sno = zipComp(["a", "b", "c"], [1, 2, 3], [{}, {}, {}]);
// const sno: [string, number, {}][];
const hmm = zipComp([""], [1], [true], [new Date()], [null],
[undefined], [Symbol()], [() => 2]);
// const hmm: [string, number, boolean, Date, null,
// undefined, symbol, () => 2][]
Looks good! The compiler happily produces the output type from the input type even with relatively long inputs.
Playground link to code
This is quite peculiar and shouldn't actually error.
<T extends any[]> means that T should be of an array type.
A workaround would be to set the following in your tsconfig.json compilerOptions object.
"skipLibCheck": true
Use function overloading:
function fn<T>(args: T[]): Something<T>
function fn<T>(...args: T[]): Something<T>
function fn<T>(...args: ([T[]]) | T[]): Something<T> {
// ...
}
Here is a way to define different signatures to your function
type Fn<T> = {
(...params: T[]): Something<T>;
(params: T[]): Something<T>;
};
const fn: Fn<number> => (...params) => {
...
};
Thanks to the help of @Etheryte i got onto the right search track.
I found the sollution i wanted which is:
function SampleFunction<T extends any[]>(
cb: (...args: T) => void,
...args: T
): void {}
let stringVariable = 'lorem';
let numberVariable = 8;
SampleFunction(
(a, b) => {},
stringVariable,
numberVariable
);
This implicitly passes the right argument types from the function into the callback
You can use the Parameters<T> utility type. The (...args: any[]) => any type in the extends allows any function to be passed as the callback regardless of its signature, and infers the rest from the passed function.
function sampleFunction<T extends (...args: any[]) => any>(
callback: T,
...args: Parameters<T>
) {}
sampleFunction((a: string, b: number) => null, "foo"); // Doesn't pass
sampleFunction((a: string, b: number) => null, "foo", "bar"); // Doesn't pass
sampleFunction((a: string, b: number) => null, "foo", 42); // Passes
sampleFunction((a: string, b: number) => null, 42, "bar"); // Doesn't pass