Skip to content

ES6 transpilation of async + super call moves properties from unquoted->quoted #21088

Closed

Description

@mprobst

TypeScript Version: Version 2.7.0-dev.20180109

Code

class A {
  test() {}
}

class B extends A {
  async test() {
    return super.test();
  }
}

Expected behavior:

TypeScript should not emit code that accesses properties by string name, as this breaks optimizers such as Closure Compiler or Uglify with property renaming enabled.

Actual behavior:

class A {
    test() { }
}
class B extends A {
    test() {
        const _super = name => super[name];
        return __awaiter(this, void 0, void 0, function* () {
            return _super("test").call(this);
        });
    }
}

_super("test") boils down to super['test'], which breaks when test was renamed by an optimizing compiler.

Activity

  1. mhegazy commented on Jan 9, 2018

    @mhegazy
    Contributor

    So what is the proposal here? this was the best solution we came up with for the super access that does not completely massacre the use code.

  2. added
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Jan 9, 2018
  3. mhegazy commented on Jan 9, 2018

    @mhegazy
    Contributor
  4. scriby commented on Jan 9, 2018

    @scriby
    const _super = super;
    return __awaiter(this, void 0, void 0, function* () {
      return _super.test.call(this);
    });
    

    I am sure I am missing something, but why doesn't that one work?

  5. rbuckton commented on Jan 9, 2018

    @rbuckton
    Contributor

    scriby This isn't valid javascript, unfortunately. super cannot be captured like an identifier. The only valid uses of super are a super-constructor call (i.e. super()), super-property access (i.e. super.test), or super-element access (i.e. super["test"]). We chose to use super-element access here as it allows us to parameterize super properties using super-element access in an arrow function closure.

    The only way to address your concern would be to inject a wrapper for every super-property access in an async function:

    // TypeScript
    class B extends A {
        async test() {
          // get
          const w = super.w;
    
          // set
          super.x = 1;
    
          // destructuring
          ({ y: super.y } = { y: 2 });
    
          // call
          super.z();
        }
    }
    
    // JavaScript
    class B extends A {
        test() {
            const _super = Object.create(null, {
              w: { get: () => super.w },
              x: { set: (value) => super.x = value },
              y: { set: (value) => super.y = value },
              z: { get: () => super.z }
            });
            return __awaiter(this, void 0, void 0, function* () {
                // get
                const w = _super.w;
    
                // set
                _super.x = 1;
    
                // destructuring
                ({ y: _super.y } = { y: 2 });
    
                // call
                _super.z.call(this);
            });
        }
    }
  6. scriby commented on Jan 9, 2018

    @scriby

    So this one would work?

    const _test = super.test;
    return __awaiter(this, void 0, void 0, function* () {
      return _test.call(this);
    });
    
  7. rbuckton commented on Jan 16, 2018

    @rbuckton
    Contributor

    scriby, not if super.test is a getter with side-effects, as the side-effects happen in the wrong place. Consider:

    class Base {
      testCounter = 0;
      get test() {
        this.testCounter++;
        return () => this.testCounter;
      }
    }
    
    class Sub extends Base {
      async fn() {
        console.log(super.test());
        console.log(super.test());
      }
    }

    You would expect it to print:

    1
    2
    

    But with the above proposal it would print:

    1
    1
    
  8. scriby commented on Jan 16, 2018

    @scriby

    Nice test case. I'll just keep guessing at it until something sticks :)

    const _test = () => super.test;
    return __awaiter(this, void 0, void 0, function* () {
      return _test().call(this);
    });
    
  9. rbuckton commented on Jan 16, 2018

    @rbuckton
    Contributor

    This is effectively the same as my proposal above, except that my proposal logically groups all of these helpers together and allows for the use of super.x as a binding target in a destructuring assignment:

    const _super = Object.create(null, {
      w: { get: () => super.w },
      x: { set: (value) => super.x = value },
      y: { set: (value) => super.y = value },
      z: { get: () => super.z }
    });
  10. scriby commented on Jan 17, 2018

    @scriby

    Ah sorry, I should have read that more carefully. Looks great!

  11. added this to the milestone on Apr 12, 2018
  12. calebegg commented on Aug 17, 2018

    @calebegg

    No idea how palatable this is, but another option would be:

    class A {
        test(arg) { }
    }
    class B extends A {
        test() {
            const _super = name => super[name];
            return __awaiter(this, void 0, void 0, function* () {
                return _super({test: [this, someArg]});
            });
        }
    }
    

    where _super then uses const [key, args] = Object.entries(obj)[0]; super[key].call(...args) instead of a string directly. Closure and Uglify would then know to rename 'test' in both places, and they'd get renamed to the same string, by design.

  13. added 4 commits that reference this issue on Aug 28, 2018
    dcf2f7e
    4034cf9
    bce7cfd
    8290a5a
  14. added a commit that references this issue on Oct 3, 2018
    f0826cf
  15. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptDomain: API: TransformsRelates to the public transform APINeeds ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions