I am encountering a minor issue with TypeScript, and I am uncertain whether it is due to a typo on my part or if TypeScript is unable to correctly infer the types. Let me provide all the necessary code to replicate the problem:
interface IRawFoo { type: string };
type FooConstructor = new (...args: any[]) => BaseFoo;
class BaseFoo { }
class Ext1Foo extends BaseFoo { }
class Ext2Foo extends BaseFoo { }
const FooConstructorMap = {
"ext1": Ext1Foo,
"ext2": Ext2Foo,
}
function getConstructor(rawFoo: IRawFoo): FooConstructor {
return FooConstructorMap[rawFoo.type];
}
function getInstance(rawFoo: IRawFoo): BaseFoo {
const fooConstructor = getConstructor(rawFoo);
return initializeFoo(rawFoo, fooConstructor);
}
function initializeFoo<T extends FooConstructor>(rawFoo: IRawFoo, constructor: T): InstanceType<T> {
const newFoo = new constructor();
/* Doing stuff for each FOO instance. */
return newFoo; // TS ERROR HERE
}
/* USAGE EXAMPLE .*/
const ext1RawFoo : IRawFoo = { type: "ext2" };
const ext1Foo = initializeFoo(ext1RawFoo, Ext1Foo); // <- In this example, ext1Foo is a "Ext1Foo".
Allow me to provide a quick explanation of the code:
- I have
BaseFoo
and several classes that extend it; - The constructors of these classes are mapped via an object;
- I need to be able to call
getInstance
which returns aBaseFoo
. Subsequently, I should be able to useinstanceof()
to determine the specific type of the returned element; - I should also be able to call
initializeFoo
by passing the constructor directly and receiving the return type without any explicit assertion;
The current code results in the following error within the return
statement of initializeFoo
:
Type 'BaseFoo' is not assignable to type 'InstanceType<T>'.ts(2322)
However, I am unsure why this error occurs, as the code logic appears sound to me.
In initializeFoo
, I am asserting that T
extends FooConstructor
, meaning that T
is an object with a constructor that generates an instance of BaseFoo
.
I then specify that initializeFoo
returns an InstanceType<T>
. Given that instantiating T
yields a BaseFoo
due to T extends FooConstructor
, I believe it is reasonable to assume that InstanceType<T>
is equivalent to BaseFoo
. However, TypeScript disagrees.
At this juncture, I am unsure if I have made an error in my code and there exists a better way to implement this, or if TypeScript lacks the intelligence to make such an assumption.
I acknowledge that I could redefine the signature of initializeFoo
like so:
function initializeFoo<T extends FooConstructor>(rawFoo: IRawFoo, constructor: T): BaseFoo
Yet, if I were to write the following:
const ext1Foo = initializeFoo(ext1RawFoo, Ext1Foo);
The type of ext1Foo
would be BaseFoo
rather than Ext1Foo
, which is not ideal.
Ultimately, I understand that I can resolve this by writing:
return newFoo as InstanceType<T>
However, using as
feels like a workaround due to poorly written code.
Thank you!