Skip to content

Commit 2745895

Browse files
committed
Alternate approach to fix super calls in async methods.
1 parent b40079e commit 2745895

14 files changed

+176
-32
lines changed

src/compiler/checker.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6756,7 +6756,6 @@ namespace ts {
67566756

67576757
if (node.parserContextFlags & ParserContextFlags.Await) {
67586758
getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments;
6759-
getNodeLinks(node).flags |= NodeCheckFlags.LexicalArguments;
67606759
}
67616760
}
67626761

@@ -6934,6 +6933,11 @@ namespace ts {
69346933

69356934
getNodeLinks(node).flags |= nodeCheckFlag;
69366935

6936+
// Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference.
6937+
if (container.kind === SyntaxKind.MethodDeclaration && container.flags & NodeFlags.Async) {
6938+
getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuper;
6939+
}
6940+
69376941
if (needToCaptureLexicalThis) {
69386942
// call expressions are allowed only in constructors so they should always capture correct 'this'
69396943
// super property access expressions can also appear in arrow functions -
@@ -9858,7 +9862,7 @@ namespace ts {
98589862
return aggregatedTypes;
98599863
}
98609864

9861-
/*
9865+
/*
98629866
*TypeScript Specification 1.0 (6.3) - July 2014
98639867
* An explicitly typed function whose return type isn't the Void or the Any type
98649868
* must have at least one return statement somewhere in its body.
@@ -9884,15 +9888,15 @@ namespace ts {
98849888
const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn;
98859889

98869890
if (returnType && !hasExplicitReturn) {
9887-
// minimal check: function has syntactic return type annotation and no explicit return statements in the body
9891+
// minimal check: function has syntactic return type annotation and no explicit return statements in the body
98889892
// this function does not conform to the specification.
9889-
// NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present
9893+
// NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present
98909894
error(func.type, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value);
98919895
}
98929896
else if (compilerOptions.noImplicitReturns) {
98939897
if (!returnType) {
98949898
// If return type annotation is omitted check if function has any explicit return statements.
9895-
// If it does not have any - its inferred return type is void - don't do any checks.
9899+
// If it does not have any - its inferred return type is void - don't do any checks.
98969900
// Otherwise get inferred return type from function body and report error only if it is not void / anytype
98979901
const inferredReturnType = hasExplicitReturn
98989902
? getReturnTypeOfSignature(getSignatureFromDeclaration(func))

src/compiler/emitter.ts

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
321321
const awaiterHelper = `
322322
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) {
323323
return new Promise(function (resolve, reject) {
324-
generator = generator.call(thisArg, _arguments);
324+
generator = generator.apply(thisArg, _arguments);
325325
function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); }
326326
function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } }
327327
function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } }
@@ -1496,11 +1496,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
14961496
}
14971497

14981498
function emitExpressionIdentifier(node: Identifier) {
1499-
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.LexicalArguments) {
1500-
write("_arguments");
1501-
return;
1502-
}
1503-
15041499
const container = resolver.getReferencedExportContainer(node);
15051500
if (container) {
15061501
if (container.kind === SyntaxKind.SourceFile) {
@@ -2287,23 +2282,72 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
22872282
write(")");
22882283
}
22892284

2285+
function isSuperPropertyAccess(node: Expression): node is PropertyAccessExpression {
2286+
return node.kind === SyntaxKind.PropertyAccessExpression
2287+
&& (<PropertyAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword;
2288+
}
2289+
2290+
function isSuperElementAccess(node: Expression): node is ElementAccessExpression {
2291+
return node.kind === SyntaxKind.ElementAccessExpression
2292+
&& (<ElementAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword;
2293+
}
2294+
2295+
function isInAsyncMethodWithSuperInES6(node: CallExpression) {
2296+
if (languageVersion === ScriptTarget.ES6) {
2297+
const container = getSuperContainer(node, /*includeFunctions*/ false);
2298+
if (container && resolver.getNodeCheckFlags(container) & NodeCheckFlags.AsyncMethodWithSuper) {
2299+
return true;
2300+
}
2301+
}
2302+
2303+
return false;
2304+
}
2305+
2306+
function emitSuperAccessInAsyncMethod(node: Expression) {
2307+
write("_super(");
2308+
emit(node);
2309+
write(")");
2310+
}
2311+
22902312
function emitCallExpression(node: CallExpression) {
22912313
if (languageVersion < ScriptTarget.ES6 && hasSpreadElement(node.arguments)) {
22922314
emitCallWithSpread(node);
22932315
return;
22942316
}
2317+
2318+
const expression = node.expression;
22952319
let superCall = false;
2296-
if (node.expression.kind === SyntaxKind.SuperKeyword) {
2297-
emitSuper(node.expression);
2320+
let isAsyncMethodWithSuper = false;
2321+
if (expression.kind === SyntaxKind.SuperKeyword) {
2322+
emitSuper(expression);
22982323
superCall = true;
22992324
}
23002325
else {
2301-
emit(node.expression);
2302-
superCall = node.expression.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.expression).expression.kind === SyntaxKind.SuperKeyword;
2326+
if (isSuperPropertyAccess(expression)) {
2327+
superCall = true;
2328+
if (isInAsyncMethodWithSuperInES6(node)) {
2329+
isAsyncMethodWithSuper = true;
2330+
const name = <StringLiteral>createSynthesizedNode(SyntaxKind.StringLiteral);
2331+
name.text = expression.name.text;
2332+
emitSuperAccessInAsyncMethod(name);
2333+
}
2334+
}
2335+
else if (isSuperElementAccess(expression)) {
2336+
superCall = true;
2337+
if (isInAsyncMethodWithSuperInES6(node)) {
2338+
isAsyncMethodWithSuper = true;
2339+
emitSuperAccessInAsyncMethod(expression.argumentExpression);
2340+
}
2341+
}
2342+
2343+
if (!isAsyncMethodWithSuper) {
2344+
emit(expression);
2345+
}
23032346
}
2304-
if (superCall && languageVersion < ScriptTarget.ES6) {
2347+
2348+
if (superCall && (languageVersion < ScriptTarget.ES6 || isAsyncMethodWithSuper)) {
23052349
write(".call(");
2306-
emitThis(node.expression);
2350+
emitThis(expression);
23072351
if (node.arguments.length) {
23082352
write(", ");
23092353
emitCommaList(node.arguments);
@@ -2980,7 +3024,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
29803024
}
29813025
else {
29823026
// this is top level converted loop so we need to create an alias for 'this' here
2983-
// NOTE:
3027+
// NOTE:
29843028
// if converted loops were all nested in arrow function then we'll always emit '_this' so convertedLoopState.thisName will not be set.
29853029
// If it is set this means that all nested loops are not nested in arrow function and it is safe to capture 'this'.
29863030
write(`var ${convertedLoopState.thisName} = this;`);
@@ -4452,6 +4496,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
44524496
write(" {");
44534497
increaseIndent();
44544498
writeLine();
4499+
4500+
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) {
4501+
write("const _super = name => super[name];");
4502+
writeLine();
4503+
}
4504+
44554505
write("return");
44564506
}
44574507

@@ -4472,12 +4522,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
44724522
}
44734523

44744524
// Emit the call to __awaiter.
4475-
if (hasLexicalArguments) {
4476-
write(", function* (_arguments)");
4477-
}
4478-
else {
4479-
write(", function* ()");
4480-
}
4525+
write(", function* ()");
44814526

44824527
// Emit the signature and body for the inner generator function.
44834528
emitFunctionBody(node);

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2022,7 +2022,7 @@ namespace ts {
20222022
SuperInstance = 0x00000100, // Instance 'super' reference
20232023
SuperStatic = 0x00000200, // Static 'super' reference
20242024
ContextChecked = 0x00000400, // Contextual types have been assigned
2025-
LexicalArguments = 0x00000800,
2025+
AsyncMethodWithSuper = 0x00000800,
20262026
CaptureArguments = 0x00001000, // Lexical 'arguments' used in body (for async functions)
20272027

20282028
// Values for enum members have been computed, and any errors have been reported for them.

tests/baselines/reference/asyncArrowFunctionCapturesArguments_es6.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ class C {
1111
class C {
1212
method() {
1313
function other() { }
14-
var fn = () => __awaiter(this, arguments, Promise, function* (_arguments) { return yield other.apply(this, _arguments); });
14+
var fn = () => __awaiter(this, arguments, Promise, function* () { return yield other.apply(this, arguments); });
1515
}
1616
}

tests/baselines/reference/asyncAwaitIsolatedModules_es6.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ module M {
4242
//// [asyncAwaitIsolatedModules_es6.js]
4343
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) {
4444
return new Promise(function (resolve, reject) {
45-
generator = generator.call(thisArg, _arguments);
45+
generator = generator.apply(thisArg, _arguments);
4646
function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); }
4747
function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } }
4848
function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } }

tests/baselines/reference/asyncAwait_es6.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ module M {
4242
//// [asyncAwait_es6.js]
4343
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) {
4444
return new Promise(function (resolve, reject) {
45-
generator = generator.call(thisArg, _arguments);
45+
generator = generator.apply(thisArg, _arguments);
4646
function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); }
4747
function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } }
4848
function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } }
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//// [asyncMethodWithSuper_es6.ts]
2+
class A {
3+
x() {
4+
}
5+
}
6+
7+
class B extends A {
8+
async y() {
9+
super.x();
10+
super["x"]();
11+
}
12+
}
13+
14+
//// [asyncMethodWithSuper_es6.js]
15+
class A {
16+
x() {
17+
}
18+
}
19+
class B extends A {
20+
y() {
21+
const _super = name => super[name];
22+
return __awaiter(this, void 0, Promise, function* () {
23+
_super("x").call(this);
24+
_super("x").call(this);
25+
});
26+
}
27+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
=== tests/cases/conformance/async/es6/asyncMethodWithSuper_es6.ts ===
2+
class A {
3+
>A : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0))
4+
5+
x() {
6+
>x : Symbol(x, Decl(asyncMethodWithSuper_es6.ts, 0, 9))
7+
}
8+
}
9+
10+
class B extends A {
11+
>B : Symbol(B, Decl(asyncMethodWithSuper_es6.ts, 3, 1))
12+
>A : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0))
13+
14+
async y() {
15+
>y : Symbol(y, Decl(asyncMethodWithSuper_es6.ts, 5, 19))
16+
17+
super.x();
18+
>super.x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9))
19+
>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0))
20+
>x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9))
21+
22+
super["x"]();
23+
>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0))
24+
>"x" : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9))
25+
}
26+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/conformance/async/es6/asyncMethodWithSuper_es6.ts ===
2+
class A {
3+
>A : A
4+
5+
x() {
6+
>x : () => void
7+
}
8+
}
9+
10+
class B extends A {
11+
>B : B
12+
>A : A
13+
14+
async y() {
15+
>y : () => Promise<void>
16+
17+
super.x();
18+
>super.x() : void
19+
>super.x : () => void
20+
>super : A
21+
>x : () => void
22+
23+
super["x"]();
24+
>super["x"]() : void
25+
>super["x"] : () => void
26+
>super : A
27+
>"x" : string
28+
}
29+
}

tests/baselines/reference/asyncMultiFile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function g() { }
88
//// [a.js]
99
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) {
1010
return new Promise(function (resolve, reject) {
11-
generator = generator.call(thisArg, _arguments);
11+
generator = generator.apply(thisArg, _arguments);
1212
function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); }
1313
function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } }
1414
function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } }

0 commit comments

Comments
 (0)