First: I will change your definition of ISmthConstructor<T>
to the following:
interface ISmthConstructor<T> {
new(value: T): ISmth<T>; // added return type
}
The version in your code returns an any
type implicitly, which is fine I guess but doesn't seem to be your intent.
Today's build of TypeScript (3.1.0-dev.20180922) gives me hope that the right answer is to tell you to wait until TypeScript 3.1 comes out sometime this month (Sep 2018) and then do this:
declare function f<T extends any[]>(
...args: { [K in keyof T]: ISmthConfig<T[K]> }
): void;
// no error, T inferred as [number, string]
f(
{ type: A, config: 12 },
{ type: B, config: "" }
);
// error, T inferred as [number, string, string | number]
f(
{ type: A, config: 12 },
{ type: B, config: "" },
{ type: A, config: "" } // error on type: string is not assignable to number
);
This is taking advantage of the upcoming mapped tuples feature, as well as inference from mapped types. Basically you are saying that the argument at position K
must be an ISmthConfig<T[K]>
. If the compiler can infer a tuple/array type T
that meets this criterion, then it succeeds. Otherwise it reports an error on the offending parameter.
Anyway this is really promising!
If you need something that works on TypeScript 3.0, the best you're going to do is to pick some maximum reasonable length of argument list and make a function that can accommodate that:
function goodEnough<A0, A1, A2, A3, A4, A5, A6>(
...args: [ISmthConfig<A0>?, ISmthConfig<A1>?, ISmthConfig<A2>?, ISmthConfig<A3>?,
ISmthConfig<A4>?, ISmthConfig<A5>?, ISmthConfig<A6>?]
) {
return args;
}
goodEnough({ type: A, config: 12 }); // okay
goodEnough({ type: B, config: "" }); // okay
goodEnough(
{ type: A, config: 12 },
{ type: B, config: "" },
{ type: A, config: "" } // error, string not assignable to number
);
This has the disadvantage of having an arbitrary limit and being verbose, but at least it works.
If the above signature ends up giving you an unusable type for args
with lots of optional tuple elements, or you need something that works in TypeScript 2.9 and below, then you can do a series of overloads, which is even more verbose:
function ugh<A0>(a0: ISmthConfig<A0>): [typeof a0];
function ugh<A0, A1>(
a0: ISmthConfig<A0>, a1: ISmthConfig<A1>
): [typeof a0, typeof a1];
function ugh<A0, A1, A2>(
a0: ISmthConfig<A0>, a1: ISmthConfig<A1>, a2: ISmthConfig<A2>
): [typeof a0, typeof a1, typeof a2];
function ugh(...args: ISmthConfig<any>[]): ISmthConfig<any>[] {
return args;
}
ugh({ type: A, config: 12 }); // okay
ugh({ type: B, config: "" }); // okay
ugh(
{ type: A, config: 12 },
{ type: B, config: "" },
{ type: A, config: "" } // error, string not assignable to number
);
That's functional, but, well, ugh.
To recap: TypeScript 3.1 will solve this nicely; wait for that (or use it now) if you can. Otherwise, there are fallbacks/workarounds of various layers of distaste.
Hope that helps. Good luck!