I'm trying to do what I think is a "textbook" use case for polymorphic this
and it isn't working and I just can't figure this out. Imagine I have some abstract
base class that is cloneable, e.g.:
abstract class X {
abstract clone(): this;
}
Now I want to implement a base class that provides a clone
implementation:
class Y extends X {
constructor(private x: number) { super() }
clone(): this { return new Y(this.x + 1); }
}
When I do this, I get an error that says Type Y is not assignable to type 'this'
. I'm totally confused. All I want to convey here is the type constraint that if a subclass of X
has its clone
method invoked, that the type of the thing that you will get back will be identical to the subtype. Isn't this exactly what polymorphic this
is for? What am I doing wrong here?
Here is a link to this code in the TypeScript playground.
I've marked artem's answer as the correct one because he seems to be correct that it really isn't possible to do this in a 100% safe way.
However, there is a way to get the compiler to enforce the type constraint I wanted. So, I decided to include this answer in case it is useful to people. The only downside of my approach is that it is slightly unsafe for exactly the reasons that artem points out, i.e., that somebody could extend your classes without providing an implementation of clone
and create a situation where the returned value would not really be what you claim it is.
My solution was to just add a cast. Again, this is unsafe in general. But if you never extend from the classes, it works fine as far as I can tell. So my solution was:
class Y extends X {
constructor(private x: number) { super() }
clone(): this {
return new Y(this.x + 1) as this;
}
}
You can see a complete version that compiles here.
No, as things are right now, it's not a supported use case for this type. A method with this
return type should return an instance of derived class even if this method is not overridden in the derived class - see code in this answer for an example that justifies this.
In other words, given abstract clone(): this
declaration, this code must be valid even if Z does not override clone
itself, but you implementation of clone
in Y
breaks it.
class Z extends Y {
f() {
let c: Z = this.clone();
}
}
So it looks like a method with this
return type should always return this
.
UPDATE: I found an open issue with this use case on github, marked as 'accepting pull requests'. I'm not sure however if they really intend to support it, or plan to fix cloneNode
in their compiler in some other way.
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