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

New operator should return object returned by constructor #38519

Open
5 tasks done
yukulele opened this issue May 13, 2020 · 10 comments
Open
5 tasks done

New operator should return object returned by constructor #38519

yukulele opened this issue May 13, 2020 · 10 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

@yukulele
Copy link

Search Terms

new, operator, constructor, return, object

Suggestion

in javascript, calling with the operator new a function that returns an object does not resolves to the created instance but to the object returned by the function

Typescript should do the same.

That could offer a better compliance with the ECMAScript standard.

Use Cases

This can for example allow to create async constructor (see examples)

Examples

function foo(returnDate = false) {
  this.bar = 1;
  if(returnDate) return new Date()
}
const obj1 = new foo() // ES resolves to a foo instance ; typescript get error "ts(2350)"
const obj2 = new foo(true) // ES resolves to a Date     ; typescript get error "ts(2350)"
obj1 instanceof foo // true
obj2 instanceof foo // false

// obj1 and obj2 type should be foo | Date

Use case example: async constructor:

class Foo {
  constructor() {
    return Promise.resolve().then(() => this);
  }
}

const foo1 = await new Foo();
const foo2 = new Foo(); // foo2 is Promise<Foo> ; ts consider foo2 as Foo

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, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@Haringat
Copy link

Haringat commented May 27, 2020

@yukulele As much as I support the request, I have my doubts that this would not break existing Typescript code. There may be code like this out in the open:

class MyDateLikeClass {
    constructor() {
        const result = new Date();
        (result as unknown as MyDateLikeClass).doX = MyDateLikeClass.prototype.doX.bind(result);
        return result;
    }
    doX() {
    }
}

function doSomething(with: MyDateLikeClass) {
}

doSomething(new MyDateLikeClass());

With your suggestion this code would break with Date is not assignable to parameter type MyDateLikeClass. As artificial as this might look, things like this are common when trying to achieve (something like) multi-inheritence.

Edit:
I have given this a bit of thought and probably it would be best if we could give constructors an explicit return type. That way existing code would not break and new code could use the feature.
The code you have in your comment would then be:

class Foo {
  constructor(): Promise<Foo> { // or maybe Promise<this> ?
    return Promise.resolve().then(() => this);
  }
}

const foo1 = await new Foo();
const foo2 = new Foo(); // foo2 is Promise<Foo> ; ts consider foo2 as Foo

@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 Jun 5, 2020
@RyanCavanaugh
Copy link
Member

I really pity anyone working with code like this - (new Foo()) instanceof Foo might be false ?

@Haringat
Copy link

Haringat commented Jun 8, 2020

@RyanCavanaugh I pity everyone who has to write declarations for code like this in current ts.
Right now you have the choice between

  • declaring a class and not give a damn about your declarations actually doing the wrong thing.
  • declaring each class which is like this as a const instance of an interface which looks like the actual class and has the return type of its new() set to the return type of the constructor and its prototype field set to the class prototype interface which you also have to create.

Obviously the first is plain wrong and the second is very tedious to write.
Of course the obvious solution to this is not to write such code in the first place but there are times when you are not the person who wrote it but still have to figure out some typings for it.

@yukulele
Copy link
Author

yukulele commented Jun 8, 2020

@RyanCavanaugh this is how ecmascript works, but I admit it's neither the most well-known feature nor a commonly expected behavior...

That said, I don't think it's that hard to work with that if the text editor is aware of the types.

const foo = new Foo()
// editor knows `foo` type is `Foo | Bar`

foo.fooMethod() // Error: Property 'fooMethod' does not exist on type 'Bar'. ts(2339)

if(foo instanceOf Foo) {
  foo.fooMethod() // ok
}

@RyanCavanaugh
Copy link
Member

+[[+""]+[2]+[1]+[+[]]] === 210 is also how ecmascript works but I wouldn't want to work in that codebase 🙃

@Haringat
Copy link

Haringat commented Jun 9, 2020

@RyanCavanaugh It does have a certain kind of esthetics to it though 😄. However, we should try to stick to the topic of the ticket.

@yukulele
Copy link
Author

yukulele commented Sep 11, 2020

[...]

  1. If the constructor function returns a non-primitive, this return value becomes the result of the whole new expression. Otherwise, if the constructor function doesn't return anything or returns a primitive, newInstance is returned instead. (Normally constructors don't return a value, but they can choose to do so to override the normal object creation process.)

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new#Description

@CMCDragonkai
Copy link

Perhaps if you allow:

async constructor () { ... }

Then you have a way of disambiguating between constructors that return the instance, and constructors that return the promise of the instance.

This shows how you can make use of the async constructor and extend it with some caveats involving private properties.

https://2ality.com/2019/11/creating-class-instances.html

@Haringat
Copy link

@CMCDragonkai why only allow this specifically for Promises when we know that there is other code that would benefit from a generalized solution?

@CMCDragonkai
Copy link

Would love for that to be available, it would mean I can finally have async constructors for classes in TS.

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

4 participants