Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Could there be a This type like the Self type in rust ? #44055

Open
5 tasks done
yw662 opened this issue May 12, 2021 · 7 comments
Open
5 tasks done

Could there be a This type like the Self type in rust ? #44055

yw662 opened this issue May 12, 2021 · 7 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@yw662
Copy link

yw662 commented May 12, 2021

Suggestion

πŸ” Search Terms

This, Self, typeof this

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

For example, let's declare an interface Cloneable, instances of classes implementing Cloneable should be able to clone themselves:

interface Cloneable {
    clone(): This
}

The return type of function clone() of interface Cloneable should be the actual type of this, or typeof this in the shown code. However typeof this does not work. It will raise TS2304 Cannot find name 'this', and TS4055 Return type of public method from exported class has or is using private name 'this'.

clone(): Cloneable is not the desired way for this interface, or the below code would pass type check:

class A implements Cloneable {
    clone() { return new B() }
}
class B implements Cloneable {
    clone() { return new A() }
}

Besides, clone() is not returning this. So clone(): this is not the right way to go. It won't allow this to pass:

class A implements Cloneable {
    clone() { return new A(this)  }
}

Generics could be a workaround, but it can be annoying for complex cases, and still can be a problem:

class A implements Cloneable<B> {
    clone() { return new B() }
}

It does not make any sense to have sth like class A implements Cloneable<B>, but generics allows it.

Allowing typeof this, or This, could be a very useful in that case.

This feature could also help in some other cases, like interface Addable:

interface Addable {
    add(that: This): This
}

The argument that of function add must be of the same type of this, so it cannot be declared as add(that: Addable): Addable. It also cannot be add(that: this): this.
It makes sense to use generics,

interface Addable<That> {
    add(that: That): That
}

But still, it is not a very good way.

πŸ“ƒ Motivating Example

πŸ’» Use Cases

As discussed above, it is mainly used by interfaces and abstract classes, like interface Cloneable, interface Addable, or maybe interface Serializable and interface Serializer.
The current approaches cannot provide the correct behavior, which is discussed above.
I am using generics, but it is incorrect indeed.

@DanielRosenwasser
Copy link
Member

Could there be a This type like the Self type in rust ?

Yes, this. πŸ˜„

interface Cloneable {
    clone(): this
}

@yw662
Copy link
Author

yw662 commented May 12, 2021

@DanielRosenwasser
Copy link
Member

Ah, apologies, I misread.

@yw662
Copy link
Author

yw662 commented May 12, 2021

Ah, apologies, I misread.

Maybe somehow not. this does not mean A, but it also does not mean exactly this, because the below code actually work:

interface Foo {
    bar(that: this): void
}

class A implements Foo {
    a: number
    constructor(a: number) {
        this.a = a
    }
    bar(that: this) {
        console.log(this.a + that.a)
    }
}

const a1 = new A(1)
const a2 = new A(2)
a1.bar(a2)

For me I would like This to be A in class A implements Cloneable, and become B in class B implements Cloneable, somehow like a generic parameter, but always passed implicitly, and always be the exact class implementing / extending the interface / abstract class.
However, since this does not mean "exactly this", maybe new A() as this could be a workaround.

@MartinJohns
Copy link
Contributor

MartinJohns commented May 12, 2021

@yw662 The fundamental problem with creating a this instance is that you have no control or knowledge over the inheritance chain. this could be a B class (which inherits from A), which has a completely different constructor than A.

This would require either something like constructor constraints for this, or allow sealed types. Or have a way to mark methods that must be implemented at every inheritance step again.

@RyanCavanaugh
Copy link
Member

The only thing missing here is

class A implements Cloneable {
    readonly a: number
    clone() {
        return new A(this.a) as this;
    }
    constructor(a: number) {
        this.a = a
    }
}

which isn't really a big hoop to jump through - it's just a disavowal of the existence of nonconforming derived classes.

@MartinJohns 's comments are apt. A derived class with no further implementation is assumed to be substitutable for its base class and breaking that assumption is a sort of big leap that requires new constraints.

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels May 12, 2021
@Ragnar-Oock
Copy link

A type like this would allow for an easy way of implementing immutable classes based on an abstract class (or just using inheritance at all really) as each methods "altering the state" should return a new instance of said class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants