diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a66a45547a0..774f1e21771 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3445,7 +3445,9 @@ namespace ts { // ES6 syntax, and requires a lexical `this` binding. if (transformFlags & TransformFlags.Super) { transformFlags ^= TransformFlags.Super; - transformFlags |= TransformFlags.ContainsSuper; + // super inside of an async function requires hoisting the super access (ES2017). + // same for super inside of an async generator, which is ESNext. + transformFlags |= TransformFlags.ContainsSuper | TransformFlags.ContainsES2017 | TransformFlags.ContainsESNext; } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; @@ -3461,7 +3463,9 @@ namespace ts { // ES6 syntax, and requires a lexical `this` binding. if (expressionFlags & TransformFlags.Super) { transformFlags &= ~TransformFlags.Super; - transformFlags |= TransformFlags.ContainsSuper; + // super inside of an async function requires hoisting the super access (ES2017). + // same for super inside of an async generator, which is ESNext. + transformFlags |= TransformFlags.ContainsSuper | TransformFlags.ContainsES2017 | TransformFlags.ContainsESNext; } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index eb29c5eba06..21e9af92604 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16180,16 +16180,18 @@ namespace ts { // // js // ... // asyncMethod() { - // const _super_asyncMethod = name => super.asyncMethod; + // const _super = Object.create(null, { + // asyncMethod: { get: () => super.asyncMethod }, + // }); // return __awaiter(this, arguments, Promise, function *() { - // let x = yield _super_asyncMethod.call(this); + // let x = yield _super.asyncMethod.call(this); // return x; // }); // } // ... // // The more complex case is when we wish to assign a value, especially as part of a destructuring assignment. As both cases - // are legal in ES6, but also likely less frequent, we emit the same more complex helper for both scenarios: + // are legal in ES6, but also likely less frequent, we only emit setters if there is an assignment: // // // ts // ... @@ -16201,17 +16203,18 @@ namespace ts { // // js // ... // asyncMethod(ar) { - // const _super_a = {get value() { return super.a; }, set value(v) { super.a = v; }}; - // const _super_b = {get value() { return super.b; }, set value(v) { super.b = v; }}; + // const _super = Object.create(null, { + // a: { get: () => super.a, set: (v) => super.a = v }, + // b: { get: () => super.b, set: (v) => super.b = v } + // }; // return __awaiter(this, arguments, Promise, function *() { - // [_super_a.value, _super_b.value] = yield ar; + // [_super.a, _super.b] = yield ar; // }); // } // ... // - // This helper creates an object with a "value" property that wraps the `super` property for both get and set. This is required for - // destructuring assignments, as a call expression cannot be used as the target of a destructuring assignment while a property - // access can. + // Creating an object that has getter and setters instead of just an accessor funtion is required for destructuring assignments + // as a call expression cannot be used as the target of a destructuring assignment while a property access can. // // For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations. if (container.kind === SyntaxKind.MethodDeclaration && hasModifier(container, ModifierFlags.Async)) { diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 8b2f5a700a0..64a126a63c8 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -62,6 +62,9 @@ namespace ts { } function visitor(node: Node): VisitResult { + if ((node.transformFlags & TransformFlags.ContainsES2017) === 0) { + return node; + } switch (node.kind) { case SyntaxKind.AsyncKeyword: // ES2017 async modifier should be elided for targets < ES2017 @@ -553,7 +556,7 @@ namespace ts { // Disable substitution in the generated super accessor itself. else if (enabledSubstitutions && substitutedSuperAccessors[getNodeId(node)]) { const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags; - enclosingSuperContainerFlags = 0 as NodeCheckFlags; + enclosingSuperContainerFlags = 0; previousOnEmitNode(hint, node, emitCallback); enclosingSuperContainerFlags = savedEnclosingSuperContainerFlags; return; @@ -592,7 +595,7 @@ namespace ts { if (node.expression.kind === SyntaxKind.SuperKeyword) { return setTextRange( createPropertyAccess( - createFileLevelUniqueName("_superProps"), + createFileLevelUniqueName("_super"), node.name), node ); @@ -642,7 +645,7 @@ namespace ts { return setTextRange( createPropertyAccess( createCall( - createFileLevelUniqueName("_super"), + createFileLevelUniqueName("_superIndex"), /*typeArguments*/ undefined, [argumentExpression] ), @@ -654,7 +657,7 @@ namespace ts { else { return setTextRange( createCall( - createFileLevelUniqueName("_super"), + createFileLevelUniqueName("_superIndex"), /*typeArguments*/ undefined, [argumentExpression] ), @@ -729,7 +732,7 @@ namespace ts { createVariableDeclarationList( [ createVariableDeclaration( - createFileLevelUniqueName("_superProps"), + createFileLevelUniqueName("_super"), /* type */ undefined, createCall( createPropertyAccess( @@ -794,14 +797,14 @@ namespace ts { name: "typescript:async-super", scoped: true, text: helperString` - const ${"_super"} = name => super[name];` + const ${"_superIndex"} = name => super[name];` }; export const advancedAsyncSuperHelper: EmitHelper = { name: "typescript:advanced-async-super", scoped: true, text: helperString` - const ${"_super"} = (function (geti, seti) { + const ${"_superIndex"} = (function (geti, seti) { const cache = Object.create(null); return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); })(name => super[name], (name, value) => super[name] = value);` diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 32f2c70a739..6d8c5d49312 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -61,6 +61,9 @@ namespace ts { } function visitorWorker(node: Node, noDestructuringValue: boolean): VisitResult { + if ((node.transformFlags & TransformFlags.ContainsESNext) === 0) { + return node; + } switch (node.kind) { case SyntaxKind.AwaitExpression: return visitAwaitExpression(node as AwaitExpression); @@ -854,7 +857,7 @@ namespace ts { if (node.expression.kind === SyntaxKind.SuperKeyword) { return setTextRange( createPropertyAccess( - createFileLevelUniqueName("_superProps"), + createFileLevelUniqueName("_super"), node.name), node ); @@ -904,7 +907,7 @@ namespace ts { return setTextRange( createPropertyAccess( createCall( - createIdentifier("_super"), + createIdentifier("_superIndex"), /*typeArguments*/ undefined, [argumentExpression] ), @@ -916,7 +919,7 @@ namespace ts { else { return setTextRange( createCall( - createIdentifier("_super"), + createIdentifier("_superIndex"), /*typeArguments*/ undefined, [argumentExpression] ), diff --git a/tests/baselines/reference/asyncMethodWithSuperConflict_es6.js b/tests/baselines/reference/asyncMethodWithSuperConflict_es6.js index 6b953843dd8..4ea8c16667a 100644 --- a/tests/baselines/reference/asyncMethodWithSuperConflict_es6.js +++ b/tests/baselines/reference/asyncMethodWithSuperConflict_es6.js @@ -77,8 +77,8 @@ class A { class B extends A { // async method with only call/get on 'super' does not require a binding simple() { - const _super_1 = name => super[name]; - const _superProps_1 = Object.create(null, { + const _superIndex = name => super[name]; + const _super_1 = Object.create(null, { x: { get: () => super.x }, y: { get: () => super.y } }); @@ -86,24 +86,24 @@ class B extends A { const _super = null; const _superProps = null; // call with property access - _superProps_1.x.call(this); + _super_1.x.call(this); // call additional property. - _superProps_1.y.call(this); + _super_1.y.call(this); // call with element access - _super_1("x").call(this); + _superIndex("x").call(this); // property access (read) - const a = _superProps_1.x; + const a = _super_1.x; // element access (read) - const b = _super_1("x"); + const b = _superIndex("x"); }); } // async method with assignment/destructuring on 'super' requires a binding advanced() { - const _super_1 = (function (geti, seti) { + const _superIndex = (function (geti, seti) { const cache = Object.create(null); return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); })(name => super[name], (name, value) => super[name] = value); - const _superProps_1 = Object.create(null, { + const _super_1 = Object.create(null, { x: { get: () => super.x, set: v => super.x = v } }); return __awaiter(this, void 0, void 0, function* () { @@ -111,21 +111,21 @@ class B extends A { const _superProps = null; const f = () => { }; // call with property access - _superProps_1.x.call(this); + _super_1.x.call(this); // call with element access - _super_1("x").value.call(this); + _superIndex("x").value.call(this); // property access (read) - const a = _superProps_1.x; + const a = _super_1.x; // element access (read) - const b = _super_1("x").value; + const b = _superIndex("x").value; // property access (assign) - _superProps_1.x = f; + _super_1.x = f; // element access (assign) - _super_1("x").value = f; + _superIndex("x").value = f; // destructuring assign with property access - ({ f: _superProps_1.x } = { f }); + ({ f: _super_1.x } = { f }); // destructuring assign with element access - ({ f: _super_1("x").value } = { f }); + ({ f: _superIndex("x").value } = { f }); }); } } diff --git a/tests/baselines/reference/asyncMethodWithSuper_es6.js b/tests/baselines/reference/asyncMethodWithSuper_es6.js index c05114c2fff..5ff89d26e94 100644 --- a/tests/baselines/reference/asyncMethodWithSuper_es6.js +++ b/tests/baselines/reference/asyncMethodWithSuper_es6.js @@ -65,51 +65,51 @@ class A { class B extends A { // async method with only call/get on 'super' does not require a binding simple() { - const _super = name => super[name]; - const _superProps = Object.create(null, { + const _superIndex = name => super[name]; + const _super = Object.create(null, { x: { get: () => super.x }, y: { get: () => super.y } }); return __awaiter(this, void 0, void 0, function* () { // call with property access - _superProps.x.call(this); + _super.x.call(this); // call additional property. - _superProps.y.call(this); + _super.y.call(this); // call with element access - _super("x").call(this); + _superIndex("x").call(this); // property access (read) - const a = _superProps.x; + const a = _super.x; // element access (read) - const b = _super("x"); + const b = _superIndex("x"); }); } // async method with assignment/destructuring on 'super' requires a binding advanced() { - const _super = (function (geti, seti) { + const _superIndex = (function (geti, seti) { const cache = Object.create(null); return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); })(name => super[name], (name, value) => super[name] = value); - const _superProps = Object.create(null, { + const _super = Object.create(null, { x: { get: () => super.x, set: v => super.x = v } }); return __awaiter(this, void 0, void 0, function* () { const f = () => { }; // call with property access - _superProps.x.call(this); + _super.x.call(this); // call with element access - _super("x").value.call(this); + _superIndex("x").value.call(this); // property access (read) - const a = _superProps.x; + const a = _super.x; // element access (read) - const b = _super("x").value; + const b = _superIndex("x").value; // property access (assign) - _superProps.x = f; + _super.x = f; // element access (assign) - _super("x").value = f; + _superIndex("x").value = f; // destructuring assign with property access - ({ f: _superProps.x } = { f }); + ({ f: _super.x } = { f }); // destructuring assign with element access - ({ f: _super("x").value } = { f }); + ({ f: _superIndex("x").value } = { f }); }); } } diff --git a/tests/baselines/reference/emitter.asyncGenerators.classMethods.es2015.js b/tests/baselines/reference/emitter.asyncGenerators.classMethods.es2015.js index 12119908d64..c7cd212d2a4 100644 --- a/tests/baselines/reference/emitter.asyncGenerators.classMethods.es2015.js +++ b/tests/baselines/reference/emitter.asyncGenerators.classMethods.es2015.js @@ -263,11 +263,11 @@ class B9 { } class C9 extends B9 { f() { - const _superProps = Object.create(null, { + const _super = Object.create(null, { g: { get: () => super.g } }); return __asyncGenerator(this, arguments, function* f_1() { - _superProps.g.call(this); + _super.g.call(this); }); } }