TypeScript intentionally does not support this behavior, see microsoft/TypeScript#2310. The TypeScript team takes the position that when writing TypeScript code (as opposed to type-checking JavaScript code) that if you want something to be new
able you should be writing a class
.
Furthermore, TypeScript doesn't model a constructor that return
s something whose type is incompatible with the class, see microsoft/TypeScript#38519.
Yes, it's true that these are possible in JavaScript, but part of the point of TypeScript is to be opinionated about which JavaScript behaviors are desirable to support, and which ones should be avoided. See What does "all legal JavaScript is legal TypeScript" mean? Of course not everyone agrees which set of behaviors fall into each category, but it's clear from the above linked issues that the TypeScript team is not interested in supporting the sort of code you're writing.
If you want to do it anyway, you'll need to work around it somehow. If you're only going to call it a few times, you could use a type assertion at each call. But if you want something more general, you can abstract the type assertion into a helper function.
That is, you write a generic helper function called, sya, asNewable
that just returns its input at runtime, but we assert that the return type is a construct signature (as well as the original function type, why not):
function asNewable<A extends any[], R>(
f: (...a: A) => R): { new(...a: A): R; (...a: A): R } {
return f as any;
}
And then
var autosense = asNewable(function (what: string) {
if (what === 'a') {
return new A();
} else {
return new B();
}
});
/* var autosense: {
(what: string): A | B;
new (what: string): A | B;
} */
Now autosense
is both a function and a constructor, according to the type system, and the following is allowed:
console.log(new autosense('a').val); // okay
Please note that since we've worked around the type system's restrictions, any problems you run into as a result are your responsibility. For example:
const oops = asNewable(() => 3)
const whoops = new oops() // compiles okay, but
// most likely a RUNTIME ERROR, oops is not a constructor
Arrow functions cannot be called with new
. There are possible ways to handle that, but that's out of scope for the question as asked. The point is that you need to be careful when circumventing TypeScript this way, that the thing you've asserted is true in the use cases you care about.
Playground link to code