I have simplified my problem to the following situation:
abstract class Foo {
child: Foo
foo(): Foo {
return this.child
}
}
class Bar extends Foo {
bar = true
}
const foo = new Bar()
// Property 'bar' does not exist on type 'Foo'. ts(2339)
foo.foo().bar
This error is self explanatory and unsurprising, but what I want to achieve is that foo() returns something of the subclass type. In this case that would be Bar, such that I can use its property bar. We could achieve this like so:
abstract class Foo<C extends Foo<C>> {
child: C
foo(): C {
return this.child
}
}
class Bar extends Foo<Bar> {
bar = true
}
const foo = new Bar()
foo.foo().bar
Yay, the error is solved! However I also want to use this inside Foo the same way I use C, i.e. the subclass type:
abstract class Foo<C extends Foo<C>> {
foo(): C {
// Type 'this' is not assignable to type 'C'.
// 'this' is assignable to the constraint of type 'C',
// but 'C' could be instantiated with a different subtype of constraint 'Foo<C>'. ts(2322)
return this
}
}
class Bar extends Foo<Bar> {
bar = true
}
const foo = new Bar()
foo.foo().bar
We can't though, as TypeScript isn't aware that the subclass type passed as a type parameter is actually the same as the actual subclass type.
Is there a way to approach this different such that I won't have this error?
I could workaround it by casting this like so: this as unknown as C, letting TypeScript know to trust us that it actually is the same, but as I have plenty of code using this in a context where C is expected, this is something I rather not do.
Edit: Rather than having a base class I started out using a few functions that contained the shared functionality:
interface Foo {
child: Foo
}
function fooFn<C extends Foo>(parent: C, child: C): C {
return child
}
class Bar implements Foo {
child: Bar
bar = true
foo() {
return fooFn(this, this.child)
}
}
const foo = new Bar()
foo.foo().bar
This worked well for my use case, but now that it became like 20 such functions, I felt the boilerplate of having those methods directly calling the functions could be replaced by a base class, but then I ran into this issue.