I'm developing a framework where some of the types are unknown since the user can return anything from an abstract method.
In this case, I want to infer what the user is returning from initialize
method, and use that type in a property of the same class. This initialize
method must return a class that extends from Base
.
The code would be something like this:
class Base {
method_1() {}
}
class Child extends Base {
method_2() {}
}
abstract class AbstractClass {
prop: C
abstract initialize(): C extends Base
}
class UserClass1 extends AbstractClass {
initialize() {
return new Base()
}
someMethod() {
this.prop.method_1() // Valid, no TS error
this.prop.method_2() // Error, it doesn't exist in Base
}
}
class UserClass2 extends AbstractClass {
initialize() {
return new Child()
}
someMethod() {
this.prop.method_1() // Valid, no TS error
this.prop.method_2() // Valid, no TS error
}
}
In resume, I have to infer C
type from initialize
method (which extends from Base
) and apply this type to prop
.
Can this be done?
You can use the polymorphic this
type to express the type of the "current" subclass of AbstractClass
. And then we can say that prop
's type is the return type of the initialize
method of this
:
abstract class AbstractClass {
prop!: ReturnType<this["initialize"]>
abstract initialize(): Base
}
Notice that ReturnType<this["initialize"]>
is using the ReturnType
utility type to extract the return type from a function type, and an indexed access type to look up the type of the initialize
property in this
.
Let's test it out:
class UserClass1 extends AbstractClass {
initialize() {
return new Base()
}
someMethod() {
this.prop.method_1() // Valid, no TS error
this.prop.method_2() // Error, it doesn't exist in Base
}
}
class UserClass2 extends AbstractClass {
initialize() {
return new Child()
}
someMethod() {
this.prop.method_1() // Valid, no TS error
this.prop.method_2() // Valid, no TS error
}
}
Looks good. In each case, prop
is given the type ReturnType<this["initialize"]>
, which for UserClass1
is known only to be assignable to Base
, whereas for UserClass2
it's known to be assignable to Child
.