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