From 2745895064bc2c1ac36ff67b101200239712a1d4 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 1 Dec 2015 11:13:54 -0800 Subject: [PATCH] Alternate approach to fix super calls in async methods. --- src/compiler/checker.ts | 14 ++-- src/compiler/emitter.ts | 83 ++++++++++++++----- src/compiler/types.ts | 2 +- ...asyncArrowFunctionCapturesArguments_es6.js | 2 +- .../asyncAwaitIsolatedModules_es6.js | 2 +- tests/baselines/reference/asyncAwait_es6.js | 2 +- .../reference/asyncMethodWithSuper_es6.js | 27 ++++++ .../asyncMethodWithSuper_es6.symbols | 26 ++++++ .../reference/asyncMethodWithSuper_es6.types | 29 +++++++ tests/baselines/reference/asyncMultiFile.js | 2 +- .../reference/reachabilityChecks7.js | 2 +- .../reference/superSymbolIndexedAccess5.js | 2 +- .../reference/superSymbolIndexedAccess6.js | 2 +- .../async/es6/asyncMethodWithSuper_es6.ts | 13 +++ 14 files changed, 176 insertions(+), 32 deletions(-) create mode 100644 tests/baselines/reference/asyncMethodWithSuper_es6.js create mode 100644 tests/baselines/reference/asyncMethodWithSuper_es6.symbols create mode 100644 tests/baselines/reference/asyncMethodWithSuper_es6.types create mode 100644 tests/cases/conformance/async/es6/asyncMethodWithSuper_es6.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9ed3ca55e19..21317bec3fb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6756,7 +6756,6 @@ namespace ts { if (node.parserContextFlags & ParserContextFlags.Await) { getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; - getNodeLinks(node).flags |= NodeCheckFlags.LexicalArguments; } } @@ -6934,6 +6933,11 @@ namespace ts { getNodeLinks(node).flags |= nodeCheckFlag; + // Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference. + if (container.kind === SyntaxKind.MethodDeclaration && container.flags & NodeFlags.Async) { + getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuper; + } + if (needToCaptureLexicalThis) { // call expressions are allowed only in constructors so they should always capture correct 'this' // super property access expressions can also appear in arrow functions - @@ -9858,7 +9862,7 @@ namespace ts { return aggregatedTypes; } - /* + /* *TypeScript Specification 1.0 (6.3) - July 2014 * An explicitly typed function whose return type isn't the Void or the Any type * must have at least one return statement somewhere in its body. @@ -9884,15 +9888,15 @@ namespace ts { const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; if (returnType && !hasExplicitReturn) { - // minimal check: function has syntactic return type annotation and no explicit return statements in the body + // minimal check: function has syntactic return type annotation and no explicit return statements in the body // this function does not conform to the specification. - // NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present + // NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present error(func.type, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); } else if (compilerOptions.noImplicitReturns) { if (!returnType) { // If return type annotation is omitted check if function has any explicit return statements. - // If it does not have any - its inferred return type is void - don't do any checks. + // If it does not have any - its inferred return type is void - don't do any checks. // Otherwise get inferred return type from function body and report error only if it is not void / anytype const inferredReturnType = hasExplicitReturn ? getReturnTypeOfSignature(getSignatureFromDeclaration(func)) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 48f9dd32aaf..d6a709fc5e7 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -321,7 +321,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { const awaiterHelper = ` var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) { return new Promise(function (resolve, reject) { - generator = generator.call(thisArg, _arguments); + generator = generator.apply(thisArg, _arguments); function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); } function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } } function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } } @@ -1496,11 +1496,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } function emitExpressionIdentifier(node: Identifier) { - if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.LexicalArguments) { - write("_arguments"); - return; - } - const container = resolver.getReferencedExportContainer(node); if (container) { if (container.kind === SyntaxKind.SourceFile) { @@ -2287,23 +2282,72 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write(")"); } + function isSuperPropertyAccess(node: Expression): node is PropertyAccessExpression { + return node.kind === SyntaxKind.PropertyAccessExpression + && (node).expression.kind === SyntaxKind.SuperKeyword; + } + + function isSuperElementAccess(node: Expression): node is ElementAccessExpression { + return node.kind === SyntaxKind.ElementAccessExpression + && (node).expression.kind === SyntaxKind.SuperKeyword; + } + + function isInAsyncMethodWithSuperInES6(node: CallExpression) { + if (languageVersion === ScriptTarget.ES6) { + const container = getSuperContainer(node, /*includeFunctions*/ false); + if (container && resolver.getNodeCheckFlags(container) & NodeCheckFlags.AsyncMethodWithSuper) { + return true; + } + } + + return false; + } + + function emitSuperAccessInAsyncMethod(node: Expression) { + write("_super("); + emit(node); + write(")"); + } + function emitCallExpression(node: CallExpression) { if (languageVersion < ScriptTarget.ES6 && hasSpreadElement(node.arguments)) { emitCallWithSpread(node); return; } + + const expression = node.expression; let superCall = false; - if (node.expression.kind === SyntaxKind.SuperKeyword) { - emitSuper(node.expression); + let isAsyncMethodWithSuper = false; + if (expression.kind === SyntaxKind.SuperKeyword) { + emitSuper(expression); superCall = true; } else { - emit(node.expression); - superCall = node.expression.kind === SyntaxKind.PropertyAccessExpression && (node.expression).expression.kind === SyntaxKind.SuperKeyword; + if (isSuperPropertyAccess(expression)) { + superCall = true; + if (isInAsyncMethodWithSuperInES6(node)) { + isAsyncMethodWithSuper = true; + const name = createSynthesizedNode(SyntaxKind.StringLiteral); + name.text = expression.name.text; + emitSuperAccessInAsyncMethod(name); + } + } + else if (isSuperElementAccess(expression)) { + superCall = true; + if (isInAsyncMethodWithSuperInES6(node)) { + isAsyncMethodWithSuper = true; + emitSuperAccessInAsyncMethod(expression.argumentExpression); + } + } + + if (!isAsyncMethodWithSuper) { + emit(expression); + } } - if (superCall && languageVersion < ScriptTarget.ES6) { + + if (superCall && (languageVersion < ScriptTarget.ES6 || isAsyncMethodWithSuper)) { write(".call("); - emitThis(node.expression); + emitThis(expression); if (node.arguments.length) { write(", "); emitCommaList(node.arguments); @@ -2980,7 +3024,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } else { // this is top level converted loop so we need to create an alias for 'this' here - // NOTE: + // NOTE: // if converted loops were all nested in arrow function then we'll always emit '_this' so convertedLoopState.thisName will not be set. // If it is set this means that all nested loops are not nested in arrow function and it is safe to capture 'this'. write(`var ${convertedLoopState.thisName} = this;`); @@ -4452,6 +4496,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write(" {"); increaseIndent(); writeLine(); + + if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) { + write("const _super = name => super[name];"); + writeLine(); + } + write("return"); } @@ -4472,12 +4522,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } // Emit the call to __awaiter. - if (hasLexicalArguments) { - write(", function* (_arguments)"); - } - else { - write(", function* ()"); - } + write(", function* ()"); // Emit the signature and body for the inner generator function. emitFunctionBody(node); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f8b26ddaa6d..dc17b9de5ec 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2022,7 +2022,7 @@ namespace ts { SuperInstance = 0x00000100, // Instance 'super' reference SuperStatic = 0x00000200, // Static 'super' reference ContextChecked = 0x00000400, // Contextual types have been assigned - LexicalArguments = 0x00000800, + AsyncMethodWithSuper = 0x00000800, CaptureArguments = 0x00001000, // Lexical 'arguments' used in body (for async functions) // Values for enum members have been computed, and any errors have been reported for them. diff --git a/tests/baselines/reference/asyncArrowFunctionCapturesArguments_es6.js b/tests/baselines/reference/asyncArrowFunctionCapturesArguments_es6.js index c24259cf0b5..fdaa365836d 100644 --- a/tests/baselines/reference/asyncArrowFunctionCapturesArguments_es6.js +++ b/tests/baselines/reference/asyncArrowFunctionCapturesArguments_es6.js @@ -11,6 +11,6 @@ class C { class C { method() { function other() { } - var fn = () => __awaiter(this, arguments, Promise, function* (_arguments) { return yield other.apply(this, _arguments); }); + var fn = () => __awaiter(this, arguments, Promise, function* () { return yield other.apply(this, arguments); }); } } diff --git a/tests/baselines/reference/asyncAwaitIsolatedModules_es6.js b/tests/baselines/reference/asyncAwaitIsolatedModules_es6.js index 7007c66ae28..9f3005e5861 100644 --- a/tests/baselines/reference/asyncAwaitIsolatedModules_es6.js +++ b/tests/baselines/reference/asyncAwaitIsolatedModules_es6.js @@ -42,7 +42,7 @@ module M { //// [asyncAwaitIsolatedModules_es6.js] var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) { return new Promise(function (resolve, reject) { - generator = generator.call(thisArg, _arguments); + generator = generator.apply(thisArg, _arguments); function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); } function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } } function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } } diff --git a/tests/baselines/reference/asyncAwait_es6.js b/tests/baselines/reference/asyncAwait_es6.js index 155a44d339d..fb4dbf9955d 100644 --- a/tests/baselines/reference/asyncAwait_es6.js +++ b/tests/baselines/reference/asyncAwait_es6.js @@ -42,7 +42,7 @@ module M { //// [asyncAwait_es6.js] var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) { return new Promise(function (resolve, reject) { - generator = generator.call(thisArg, _arguments); + generator = generator.apply(thisArg, _arguments); function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); } function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } } function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } } diff --git a/tests/baselines/reference/asyncMethodWithSuper_es6.js b/tests/baselines/reference/asyncMethodWithSuper_es6.js new file mode 100644 index 00000000000..315ce5fdb90 --- /dev/null +++ b/tests/baselines/reference/asyncMethodWithSuper_es6.js @@ -0,0 +1,27 @@ +//// [asyncMethodWithSuper_es6.ts] +class A { + x() { + } +} + +class B extends A { + async y() { + super.x(); + super["x"](); + } +} + +//// [asyncMethodWithSuper_es6.js] +class A { + x() { + } +} +class B extends A { + y() { + const _super = name => super[name]; + return __awaiter(this, void 0, Promise, function* () { + _super("x").call(this); + _super("x").call(this); + }); + } +} diff --git a/tests/baselines/reference/asyncMethodWithSuper_es6.symbols b/tests/baselines/reference/asyncMethodWithSuper_es6.symbols new file mode 100644 index 00000000000..2b7388702f1 --- /dev/null +++ b/tests/baselines/reference/asyncMethodWithSuper_es6.symbols @@ -0,0 +1,26 @@ +=== tests/cases/conformance/async/es6/asyncMethodWithSuper_es6.ts === +class A { +>A : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) + + x() { +>x : Symbol(x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) + } +} + +class B extends A { +>B : Symbol(B, Decl(asyncMethodWithSuper_es6.ts, 3, 1)) +>A : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) + + async y() { +>y : Symbol(y, Decl(asyncMethodWithSuper_es6.ts, 5, 19)) + + super.x(); +>super.x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) +>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) +>x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) + + super["x"](); +>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) +>"x" : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) + } +} diff --git a/tests/baselines/reference/asyncMethodWithSuper_es6.types b/tests/baselines/reference/asyncMethodWithSuper_es6.types new file mode 100644 index 00000000000..7786b417e81 --- /dev/null +++ b/tests/baselines/reference/asyncMethodWithSuper_es6.types @@ -0,0 +1,29 @@ +=== tests/cases/conformance/async/es6/asyncMethodWithSuper_es6.ts === +class A { +>A : A + + x() { +>x : () => void + } +} + +class B extends A { +>B : B +>A : A + + async y() { +>y : () => Promise + + super.x(); +>super.x() : void +>super.x : () => void +>super : A +>x : () => void + + super["x"](); +>super["x"]() : void +>super["x"] : () => void +>super : A +>"x" : string + } +} diff --git a/tests/baselines/reference/asyncMultiFile.js b/tests/baselines/reference/asyncMultiFile.js index e93dc586255..fa4a210c252 100644 --- a/tests/baselines/reference/asyncMultiFile.js +++ b/tests/baselines/reference/asyncMultiFile.js @@ -8,7 +8,7 @@ function g() { } //// [a.js] var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) { return new Promise(function (resolve, reject) { - generator = generator.call(thisArg, _arguments); + generator = generator.apply(thisArg, _arguments); function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); } function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } } function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } } diff --git a/tests/baselines/reference/reachabilityChecks7.js b/tests/baselines/reference/reachabilityChecks7.js index c78f99953e9..f3a27dfdab8 100644 --- a/tests/baselines/reference/reachabilityChecks7.js +++ b/tests/baselines/reference/reachabilityChecks7.js @@ -33,7 +33,7 @@ let x1 = () => { use("Test"); } //// [reachabilityChecks7.js] var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) { return new Promise(function (resolve, reject) { - generator = generator.call(thisArg, _arguments); + generator = generator.apply(thisArg, _arguments); function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); } function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } } function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } } diff --git a/tests/baselines/reference/superSymbolIndexedAccess5.js b/tests/baselines/reference/superSymbolIndexedAccess5.js index 64a8ac5094c..2626051706c 100644 --- a/tests/baselines/reference/superSymbolIndexedAccess5.js +++ b/tests/baselines/reference/superSymbolIndexedAccess5.js @@ -34,7 +34,7 @@ var Bar = (function (_super) { _super.apply(this, arguments); } Bar.prototype[symbol] = function () { - return _super.prototype[symbol](); + return _super.prototype[symbol].call(this); }; return Bar; })(Foo); diff --git a/tests/baselines/reference/superSymbolIndexedAccess6.js b/tests/baselines/reference/superSymbolIndexedAccess6.js index e014cf47c1a..48834422e24 100644 --- a/tests/baselines/reference/superSymbolIndexedAccess6.js +++ b/tests/baselines/reference/superSymbolIndexedAccess6.js @@ -34,7 +34,7 @@ var Bar = (function (_super) { _super.apply(this, arguments); } Bar[symbol] = function () { - return _super[symbol](); + return _super[symbol].call(this); }; return Bar; })(Foo); diff --git a/tests/cases/conformance/async/es6/asyncMethodWithSuper_es6.ts b/tests/cases/conformance/async/es6/asyncMethodWithSuper_es6.ts new file mode 100644 index 00000000000..2225200ef60 --- /dev/null +++ b/tests/cases/conformance/async/es6/asyncMethodWithSuper_es6.ts @@ -0,0 +1,13 @@ +// @target: ES6 +// @noEmitHelpers: true +class A { + x() { + } +} + +class B extends A { + async y() { + super.x(); + super["x"](); + } +} \ No newline at end of file