user11406
user11406

Reputation: 1258

Default parameters with generics in TypeScript

Consider the last example of generics from the TypeScript Docs: https://www.typescriptlang.org/docs/handbook/generics.html#using-class-types-in-generics

If I wanted to make Lion the default value for createInstance() how would I do this?

For example:

class Animal {
  numLegs: number = 0;
}

class Lion extends Animal {
  keeper = "zookeeper";
}

function createInstance<A extends Animal>(c: new () => A = Lion): A { // error!
// -------------------------------------> ~~~~~~~~~~~~~~~~~~~~~
//  'Lion' is assignable to the constraint of type 'A', but 'A' could be 
// instantiated with a different subtype of constraint 'Animal'.
  return new c();
}

Playground link

Upvotes: 12

Views: 5424

Answers (1)

jcalz
jcalz

Reputation: 328152

There is currently no way to do this with compiler-verified type safety. See microsoft/TypeScript#58977 for the relevant feature request. Right now all the approaches involve working around this limitation.


You could use a generic parameter default and a type assertion to tell the compiler that a lack of inference for A implies that A is Lion, and that it's okay to make that assumption:

function createInstanceAssert<A extends Animal = Lion>(c: new () => A = Lion as any) {
  return new c();
}

This will work as desired in your use cases, I think:

createInstanceAssert(Lion).keeper.nametag;
createInstanceAssert(Bee).keeper.hasMask;
createInstanceAssert().keeper.nametag;

Although there is the possibility of someone manually specifying the generic parameter and causing trouble:

createInstanceAssert<Bee>().keeper.hasMask.valueOf(); // compiles, but error at runtime
// ---------------> ~~~~~ don't do this, okay?

That's probably not likely, but you should be aware of it.


If you really want to prevent misuse you could use overloads to distinguish the two separate use cases:

function createInstanceOverload<A extends Animal>(c: new () => A): A;
function createInstanceOverload(): Lion;
function createInstanceOverload(c: new () => Animal = Lion) {
  return new c();
}

You can basically call that with a parameter, in which case it is generic and A is inferred, or you can call it with no parameter, in which case it is not generic, and a Lion comes out:

createInstanceOverload(Lion).keeper.nametag;
createInstanceOverload(Bee).keeper.hasMask;
createInstanceOverload().keeper.nametag;

Since there is no generic zero-arg call signature, the following is no longer possible without a big compiler error:

createInstanceOverload<Bee>().keeper.hasMask.valueOf(); // error at compile time

Playground link to code

Upvotes: 10

Related Questions