Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic generic types in TypeScript?

I'd like to make a function in TypeScript that takes an array of constructor functions and returns a corresponding array of instances. See code below.

Note that the getArray method is wildly incorrect, it is just an attempt to convey my intent.

Is this possible in any form or is it beyond the capabilities of TypeScript's type engine?

class T1 {}
class T2 {}
class T3 {}
type AnyCon = new() => any;

function getOne<T_Con extends AnyCon>(con: T_Con): InstanceType<T_Con> {
  return new con();
}

function getArray<T_Cons>(cons: T_Cons): InstanceType<T_Cons>[] {
  return cons.map( (c: con) => new con() );
}

let t1: T1 = getOne(T1);
let [t2, t3]: [T2, T3] = getArray( [T2, T3] );
like image 501
radman Avatar asked Feb 15 '19 02:02

radman


People also ask

What is Variadic generic?

Variadic generics are necessary for an Array that is generic in an arbitrary number of axes to be cleanly defined as a single class.

What is new in TypeScript 4. 0?

TypeScript 4.0 improves the inference process for rest parameters and rest tuple elements so that we can type this and have it “just work”. In this case, partialCall understands which parameters it can and can't initially take, and returns functions that appropriately accept and reject anything left over.

What is a Variadic tuple?

Variadic tuples A variadic tuple type is a tuple type that has the same properties — defined length and the type of each element is known — but where the exact shape is yet to be defined.

What is rest parameter in TypeScript?

A rest parameter allows us to pass zero or any number of arguments of the specified type to a function. In the function definition where we specify function parameters rest parameters should always come at last or the typescript compiler will raise errors.


1 Answers

You can do this in TS3.1 and above using mapped array/tuple types. It's a little easier to get tuples inferred for rest parameters than it is for array parameters, so I'll show that instead:

function getVariadic<T extends Array<AnyCon>>(...cons: T): {
  [K in keyof T]: T[K] extends AnyCon ? InstanceType<T[K]> : never
};
function getVariadic(...cons: AnyCon[]): any[] {
  return cons.map((c: AnyCon) => new c());
}

let [t2, t3]: [T2, T3] = getVariadic(T2, T3);

Edit: Since TypeScript 4.0 introduced variadic tuple types you can also get the compiler to infer tuple types from arrays passed in:

function getArray<T extends Array<AnyCon>>(cons: [...T]): {
    [K in keyof T]: T[K] extends AnyCon ? InstanceType<T[K]> : never
};
function getArray(cons: AnyCon[]): any[] {
    return cons.map((c: AnyCon) => new c());
}

let [t2, t3]: [T2, T3] = getArray([T2, T3]);

Note how the cons parameter is of variadic tuple type [...T], which gives a hint to the compiler to infer that [T2, T3] is a tuple instead of an unordered array.

Playground link to code

like image 50
jcalz Avatar answered Sep 30 '22 03:09

jcalz