Why is it wrong to use InstanceType
on generics? Whether it's covariant or contravariant?
interface Ctor {
new(): Instance;
}
interface Instance {
print(): void;
}
function f1<T extends Ctor>(ctor: T) {
// Error: Type 'Instance' is not assignable to Type 'InstanceType<T>'
const ins: InstanceType<T> = new ctor();
ins.print();
}
function f2(ctor: Ctor) {
// No error
const ins: InstanceType<Ctor> = new ctor();
ins.print();
}
Playground Link
Here's what I think is the problem: Typescript determines the compile-time type of the expression new ctor()
by looking at the type of ctor
(which is T
), checking that T
must have a constructor signature (it does), and then finding what the constructor signature of T
returns. But T
could be any subtype of Ctor
, so its constructor signature could return any subtype of Instance
.
There are two reasonable things a compiler could do in this case:
R extends Instance
) and let the type of the expression new ctor()
be R
,Instance
.Typescript does the latter, perhaps because Typescript only creates type variables if you tell it to; it never does so implicitly. This gives the expression a weaker type than necessary for the assignment to InstanceType<T>
to typecheck, because InstanceType<T>
does explicitly create a type variable R
(using an infer
clause in a conditional type), which may be an arbitrary subtype of Instance
.
So the type of the expression new ctor()
is Instance
, but the assignment target's type annotation is (not really, but in some sense) a type variable R
which could be any subtype of Instance
, hence there is a type error.
A possible solution is to explicitly create a type variable for the return type, like so:
function f1<R extends Instance, T extends {new(): R}>(ctor: T) {
const ins: R = new ctor();
ins.print();
}
That said, this is only worth doing if your function deals with R
in some other capacity, such as having a return type involving R
. Otherwise, your code using ins
has to work for any realisation of R
including Instance
itself, so you might as well declare its type as Instance
anyway. (Likewise, if your function doesn't use T
anywhere else, you might as well make the parameter type Ctor
instead).
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