Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic this in TypeScript

Tags:

typescript

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.

like image 950
Michael Tiller Avatar asked Nov 13 '16 15:11

Michael Tiller


2 Answers

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.

like image 81
Michael Tiller Avatar answered Nov 15 '22 10:11

Michael Tiller


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.

like image 25
artem Avatar answered Nov 15 '22 10:11

artem