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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With