Reputation: 1258
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();
}
Upvotes: 12
Views: 5424
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
Upvotes: 10