leebyron/async-to-gen

Support for super keyword in classes

ilkkao opened this issue · 11 comments

This fails because of the wrapper function:

class Base  {
  async foo() { }
}

class Something extends Base {
    async foo() {
        return await super.foo();
    }
}

This is not an issue actually yet. super keyword is supported only starting from V8 5.2. Node master branch has V8 5.1 and 6.3.1 (latest stable) has V8 5.0.

I'm using unmerged PR that updates node to V8 5.2.

Hmm - this one might unfortunately be a wontfix and become a known limitation since the super scope is overridden by the wrapping generator function. Arrow-functions would not override the scope, but there is not arrow-function equivalent for generators.

I'm open to ideas if there is a straightforward transform that will fix this that I'm missing.

It seems like Babel also suffers from this issue unless you also include Babel's class transforming plugin as well (which replaces super keyword with prototype lookups, averting the issue)

As you said arrow functions keep the same scope. This kind of hack seems to work (tested with Node 6.3.1)

function within1() {return __async(function*(){
  function within2() {return __async(function*(){
    return yield (() =>__async(function*(){ return yield this}.bind(this)))
  }.bind(this))}
})}

function __async(f){var g=f();return new Promise(function(s,j){function c(a,x){try{var r=g[x?"throw":"next"](a)}catch(e){return j(e)}return r.done?s(r.value):Promise.resolve(r.value).then(c,d)}function d(e){return c(e,1)}c()})}

class Animal {
    say() {
        console.log('animal');
    }
}

class Dog extends Animal {
    say() {
        const _rndm = method => super[method]();
        return __async(function*(){
            _rndm('say'); // super.say();
        }.bind(this))
    }
}

const a = new Dog();
a.say();

Interesting. What about providing arguments?

function within1() {return __async(function*(){
  function within2() {return __async(function*(){
    return yield (() =>__async(function*(){ return yield this}.bind(this)))
  }.bind(this))}
})}

function __async(f){var g=f();return new Promise(function(s,j){function c(a,x){try{var r=g[x?"throw":"next"](a)}catch(e){return j(e)}return r.done?s(r.value):Promise.resolve(r.value).then(c,d)}function d(e){return c(e,1)}c()})}

class Animal {
    say(first, second) {
        console.log(`animal: ${first}, ${second}`);
    }
}

class Dog extends Animal {
    say() {
        const _rndm = (method, ...rest) => super[method](...rest);
        return __async(function*(){
            _rndm('say', 'foo', 'bar'); // super.say('foo', 'bar');
        }.bind(this))
    }
}

const a = new Dog();
a.say();

Cool idea!

I'm going to reopen, but only actually implement this once a shipped build of node includes super support to try something like this.

I checked http://kangax.github.io/compat-table/es6/ and realized that MDN has incorrect information. Node 6 (and Chrome >48) support all forms of super() without flags. Checked that myself. My comment above about the status is wrong.

This would be useful already today. Babel probably can't assume that native arrow functions are available if generators are. This library should be able.

Cool I also reproduced that Node 6 does in fact parse and use super expressions! I agree that it would be useful today to support them.

We also can't assume arrow function support if generators are supported (Node 0.12 introduced generators, Node 4 introduced arrows), however we can assume arrow function support if super expressions are supported (Node 6).

Next is to come up with minimal transforms that will account for all forms of super usage, some of which are going to be pretty tricky I think:

class SuperDuper extends BaseClass {
  constructor(arg) {
    super(arg)
  }

  static async fooAsync() {
    const arg = super.arg
    super.arg = arg
    return super.fooAsync(arg)
  }

  async barAsync() {
    const arg = super.arg
    super.arg = arg
    return super.barAsync(arg)
  }

  async bazAsync() {
    const arg = super['arg']
    super['arg'] = arg
    return super[arg](arg)
  }
}

Constructors cannot be async, so we don't need to handle super calls.

The remainder:

  • super.ident
  • super.ident(arg)
  • super.ident = val
  • delete super.ident (Should throw)
  • super[expr]
  • super[expr](arg)
  • super[expr] = val
  • delete super[expr] (Should throw)

Added a PR which should support this, using your idea of the scoped arrow function.

I think the right thing to do is just not touch deletion statements. They're runtime errors (I think they probably should have been early static errors or even parse errors) so we can just let the error happen.

I decided it would be easiest to treat super.ident and super.ident(arg) both as "get" forms, the latter being called with this.

Ultimate translation:

Before After
super.ident $uper("ident")
super.ident(arg) $uper("ident").call(this,arg)
super.ident = val $uperEq("ident" , val)
delete super.ident delete super.ident
super[expr] $uper(expr)
super[expr](arg) $uper(expr).call(this,arg)
super[expr] = val $uperEq(expr , val)
delete super[expr] delete super[expr]

Great! Now I can see only one small issue in my pretty large node app that uses async/await extensively and super in its models.

I'll report it separately.