Skip to content

Commit 2891a1d

Browse files
committed
Cleaned up async return type check
1 parent 7443ecc commit 2891a1d

34 files changed

+530
-168
lines changed

src/compiler/checker.ts

Lines changed: 28 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,7 @@ namespace ts {
824824
}
825825

826826
// Resolves a qualified name and any involved aliases
827-
function resolveEntityName(name: EntityName | Expression, meaning: SymbolFlags, location?: Node): Symbol {
827+
function resolveEntityName(name: EntityName | Expression, meaning: SymbolFlags): Symbol {
828828
if (nodeIsMissing(name)) {
829829
return undefined;
830830
}
@@ -833,7 +833,7 @@ namespace ts {
833833
if (name.kind === SyntaxKind.Identifier) {
834834
let message = meaning === SymbolFlags.Namespace ? Diagnostics.Cannot_find_namespace_0 : Diagnostics.Cannot_find_name_0;
835835

836-
symbol = resolveName(location || name, (<Identifier>name).text, meaning, message, <Identifier>name);
836+
symbol = resolveName(name, (<Identifier>name).text, meaning, message, <Identifier>name);
837837
if (!symbol) {
838838
return undefined;
839839
}
@@ -842,7 +842,7 @@ namespace ts {
842842
let left = name.kind === SyntaxKind.QualifiedName ? (<QualifiedName>name).left : (<PropertyAccessExpression>name).expression;
843843
let right = name.kind === SyntaxKind.QualifiedName ? (<QualifiedName>name).right : (<PropertyAccessExpression>name).name;
844844

845-
let namespace = resolveEntityName(left, SymbolFlags.Namespace, location);
845+
let namespace = resolveEntityName(left, SymbolFlags.Namespace);
846846
if (!namespace || namespace === unknownSymbol || nodeIsMissing(right)) {
847847
return undefined;
848848
}
@@ -8839,7 +8839,6 @@ namespace ts {
88398839
}
88408840

88418841
if (produceDiagnostics) {
8842-
checkCollisionWithAwaiterVariablesInGeneratedCode(node, node.name);
88438842
checkCollisionWithArgumentsInGeneratedCode(node);
88448843
if (compilerOptions.noImplicitAny && !node.type) {
88458844
switch (node.kind) {
@@ -9632,18 +9631,18 @@ namespace ts {
96329631
* a `resolve` function as one of its arguments and results in an object with a
96339632
* callable `then` signature.
96349633
*/
9635-
function checkAsyncFunctionReturnType(node: SignatureDeclaration): Type {
9634+
function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration): Type {
96369635
let globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType();
96379636
if (globalPromiseConstructorLikeType === emptyObjectType) {
96389637
// If we couldn't resolve the global PromiseConstructorLike type we cannot verify
96399638
// compatibility with __awaiter.
96409639
return unknownType;
96419640
}
96429641

9643-
// The return type of an async function will be the type of the instance. For this
9644-
// to be a type compatible with our async function emit, we must also check that
9645-
// the type of the declaration (e.g. the static side or "constructor" type of the
9646-
// promise) is a compatible `PromiseConstructorLike`.
9642+
// As part of our emit for an async function, we will need to emit the entity name of
9643+
// the return type annotation as an expression. To meet the necessary runtime semantics
9644+
// for __awaiter, we must also check that the type of the declaration (e.g. the static
9645+
// side or "constructor" of the promise type) is compatible `PromiseConstructorLike`.
96479646
//
96489647
// An example might be (from lib.es6.d.ts):
96499648
//
@@ -9666,36 +9665,33 @@ namespace ts {
96669665
//
96679666
// When we get the type of the `Promise` symbol here, we get the type of the static
96689667
// side of the `Promise` class, which would be `{ new <T>(...): Promise<T> }`.
9669-
let returnType = getTypeFromTypeNode(node.type);
9670-
let entityName = getEntityNameFromTypeNode(node.type);
9671-
let resolvedName = entityName ? resolveEntityName(entityName, SymbolFlags.Value, node) : undefined;
9672-
if (!resolvedName || !returnType.symbol) {
9673-
error(node, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type);
9674-
return unknownType;
9668+
9669+
let promiseType = getTypeFromTypeNode(node.type);
9670+
let promiseConstructor = getMergedSymbol(promiseType.symbol);
9671+
if (!promiseConstructor || !symbolIsValue(promiseConstructor)) {
9672+
error(node, Diagnostics.Type_0_is_not_a_valid_async_function_return_type, typeToString(promiseType));
9673+
return unknownType
96759674
}
9676-
9677-
if (getMergedSymbol(resolvedName) !== getMergedSymbol(returnType.symbol)) {
9678-
// If we were unable to resolve the return type as a value, report an error.
9679-
let identifier = getFirstIdentifier(entityName);
9680-
error(resolvedName.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions,
9681-
identifier.text,
9682-
identifier.text);
9675+
9676+
// Validate the promise constructor type.
9677+
let promiseConstructorType = getTypeOfSymbol(promiseConstructor);
9678+
if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, node, Diagnostics.Type_0_is_not_a_valid_async_function_return_type)) {
96839679
return unknownType;
96849680
}
96859681

9686-
// When we emit the async function, we need to ensure we emit any imports that might
9687-
// otherwise have been elided if the return type were only ever referenced in a type
9688-
// position. As such, we check the entity name as an expression.
9689-
let declaredType = checkExpression(entityName);
9690-
9691-
if (!isTypeAssignableTo(declaredType, globalPromiseConstructorLikeType)) {
9692-
// If the declared type of the return type is not assignable to a PromiseConstructorLike, report an error.
9693-
error(node, ts.Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type);
9682+
// Verify there is no local declaration that could collide with the promise constructor.
9683+
let promiseName = getEntityNameFromTypeNode(node.type);
9684+
let root = getFirstIdentifier(promiseName);
9685+
let rootSymbol = getSymbol(node.locals, root.text, SymbolFlags.Value);
9686+
if (rootSymbol) {
9687+
error(rootSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions,
9688+
root.text,
9689+
getFullyQualifiedName(promiseConstructor));
96949690
return unknownType;
96959691
}
9696-
9692+
96979693
// Get and return the awaited type of the return type.
9698-
return getAwaitedType(returnType, node, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type);
9694+
return getAwaitedType(promiseType, node, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type);
96999695
}
97009696

97019697
/** Check a decorator */
@@ -10093,22 +10089,6 @@ namespace ts {
1009310089
}
1009410090
}
1009510091
}
10096-
10097-
function checkCollisionWithAwaiterVariablesInGeneratedCode(node: Node, name: DeclarationName): void {
10098-
if (!name || name.kind !== SyntaxKind.Identifier || isTypeNode(name)) {
10099-
return;
10100-
}
10101-
10102-
let identifier = <Identifier>name;
10103-
let container = getContainingFunction(name);
10104-
if (container && isAsyncFunctionLike(container) && node.kind !== SyntaxKind.Identifier) {
10105-
let promiseConstructorName = getEntityNameFromTypeNode(container.type);
10106-
let firstIdentifier = promiseConstructorName ? getFirstIdentifier(promiseConstructorName) : undefined;
10107-
if (firstIdentifier && firstIdentifier.text === identifier.text) {
10108-
error(node, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, identifier.text, getTextOfNode(promiseConstructorName));
10109-
}
10110-
}
10111-
}
1011210092

1011310093
// Check that a parameter initializer contains no references to parameters declared to the right of itself
1011410094
function checkParameterInitializer(node: VariableLikeDeclaration): void {
@@ -10954,7 +10934,6 @@ namespace ts {
1095410934
checkTypeNameIsReserved(node.name, Diagnostics.Class_name_cannot_be_0);
1095510935
checkCollisionWithCapturedThisVariable(node, node.name);
1095610936
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
10957-
checkCollisionWithAwaiterVariablesInGeneratedCode(node, node.name);
1095810937
}
1095910938
checkTypeParameters(node.typeParameters);
1096010939
checkExportsOnMergedDeclarations(node);
@@ -11398,7 +11377,6 @@ namespace ts {
1139811377
checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0);
1139911378
checkCollisionWithCapturedThisVariable(node, node.name);
1140011379
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
11401-
checkCollisionWithAwaiterVariablesInGeneratedCode(node, node.name);
1140211380
checkExportsOnMergedDeclarations(node);
1140311381

1140411382
computeEnumMemberValues(node);

src/compiler/diagnosticInformationMap.generated.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ namespace ts {
4848
A_set_accessor_parameter_cannot_have_an_initializer: { code: 1052, category: DiagnosticCategory.Error, key: "A 'set' accessor parameter cannot have an initializer." },
4949
A_set_accessor_cannot_have_rest_parameter: { code: 1053, category: DiagnosticCategory.Error, key: "A 'set' accessor cannot have rest parameter." },
5050
A_get_accessor_cannot_have_parameters: { code: 1054, category: DiagnosticCategory.Error, key: "A 'get' accessor cannot have parameters." },
51+
Type_0_is_not_a_valid_async_function_return_type: { code: 1055, category: DiagnosticCategory.Error, key: "Type '{0}' is not a valid async function return type." },
5152
Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher: { code: 1056, category: DiagnosticCategory.Error, key: "Accessors are only available when targeting ECMAScript 5 and higher." },
5253
An_async_function_or_method_must_have_a_valid_awaitable_return_type: { code: 1057, category: DiagnosticCategory.Error, key: "An async function or method must have a valid awaitable return type." },
5354
Operand_for_await_does_not_have_a_valid_callable_then_member: { code: 1058, category: DiagnosticCategory.Error, key: "Operand for 'await' does not have a valid callable 'then' member." },

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@
179179
"category": "Error",
180180
"code": 1054
181181
},
182+
"Type '{0}' is not a valid async function return type.": {
183+
"category": "Error",
184+
"code": 1055
185+
},
182186
"Accessors are only available when targeting ECMAScript 5 and higher.": {
183187
"category": "Error",
184188
"code": 1056

src/compiler/emitter.ts

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
4949
};`;
5050

5151
const awaiterHelper = `
52-
var __awaiter = (this && this.__awaiter) || function (generator, thisArg, args, PromiseConstructor) {
53-
PromiseConstructor || (PromiseConstructor = Promise);
52+
var __awaiter = (this && this.__awaiter) || function (args, generator) {
53+
var PromiseConstructor = args[1] || Promise;
5454
return new PromiseConstructor(function (resolve, reject) {
55-
generator = generator.call(thisArg, args);
55+
generator = generator.call(args[0], args[2]);
5656
function cast(value) { return value instanceof PromiseConstructor ? value : new PromiseConstructor(function (resolve) { resolve(value); }); }
5757
function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } }
5858
function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } }
@@ -3359,6 +3359,7 @@ var __awaiter = (this && this.__awaiter) || function (generator, thisArg, args,
33593359
function emitAsyncFunctionBodyForES6(node: FunctionLikeDeclaration) {
33603360
let promiseConstructor = getEntityNameFromTypeNode(node.type);
33613361
let isArrowFunction = node.kind === SyntaxKind.ArrowFunction;
3362+
let hasLexicalArguments = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.CaptureArguments) !== 0;
33623363
let args: string;
33633364

33643365
// An async function is emit as an outer function that calls an inner
@@ -3373,7 +3374,7 @@ var __awaiter = (this && this.__awaiter) || function (generator, thisArg, args,
33733374
// let a = async (b) => { await b; }
33743375
//
33753376
// // output
3376-
// let a = (b) => __awaiter(function* (b) {
3377+
// let a = (b) => __awaiter([this], function* (b) {
33773378
// yield b;
33783379
// }, this);
33793380
//
@@ -3383,9 +3384,9 @@ var __awaiter = (this && this.__awaiter) || function (generator, thisArg, args,
33833384
// let a = async (b) => { await arguments[0]; }
33843385
//
33853386
// // output
3386-
// let a = (b) => __awaiter(function* (arguments) {
3387+
// let a = (b) => __awaiter([this, arguments], function* (arguments) {
33873388
// yield arguments[0];
3388-
// }, this, arguments);
3389+
// });
33893390
//
33903391
// The emit for an async function expression without a lexical `arguments` binding
33913392
// might be:
@@ -3397,7 +3398,7 @@ var __awaiter = (this && this.__awaiter) || function (generator, thisArg, args,
33973398
//
33983399
// // output
33993400
// let a = function (b) {
3400-
// return __awaiter(function* () {
3401+
// return __awaiter([this], function* () {
34013402
// yield b;
34023403
// }, this);
34033404
// }
@@ -3412,9 +3413,24 @@ var __awaiter = (this && this.__awaiter) || function (generator, thisArg, args,
34123413
//
34133414
// // output
34143415
// let a = function (b) {
3415-
// return __awaiter(function* (arguments) {
3416+
// return __awaiter([this, arguments], function* (arguments) {
3417+
// yield arguments[0];
3418+
// });
3419+
// }
3420+
//
3421+
// The emit for an async function expression with a lexical `arguments` binding
3422+
// and a return type annotation might be:
3423+
//
3424+
// // input
3425+
// let a = async function (b): MyPromise<any> {
3426+
// await arguments[0];
3427+
// }
3428+
//
3429+
// // output
3430+
// let a = function (b) {
3431+
// return __awaiter([this, arguments, MyPromise], function* (arguments) {
34163432
// yield arguments[0];
3417-
// }, this, arguments);
3433+
// });
34183434
// }
34193435
//
34203436

@@ -3427,42 +3443,27 @@ var __awaiter = (this && this.__awaiter) || function (generator, thisArg, args,
34273443
write("return");
34283444
}
34293445

3446+
write(" __awaiter([this");
3447+
if (promiseConstructor || hasLexicalArguments) {
3448+
write(", ");
3449+
if (promiseConstructor) {
3450+
emitNodeWithoutSourceMap(promiseConstructor);
3451+
}
3452+
if (hasLexicalArguments) {
3453+
write(", arguments");
3454+
}
3455+
}
34303456

34313457
// Emit the call to __awaiter.
3432-
let hasLexicalArguments = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.CaptureArguments) !== 0;
34333458
if (hasLexicalArguments) {
3434-
write(" __awaiter(function* (arguments)");
3459+
write("], function* (arguments)");
34353460
}
34363461
else {
3437-
write(" __awaiter(function* ()");
3462+
write("], function* ()");
34383463
}
34393464

34403465
// Emit the signature and body for the inner generator function.
34413466
emitFunctionBody(node);
3442-
3443-
// Emit the current `this` binding.
3444-
write(",");
3445-
writeLine();
3446-
write("this");
3447-
3448-
// Optionally emit the lexical arguments.
3449-
if (hasLexicalArguments) {
3450-
write(", arguments");
3451-
}
3452-
3453-
// If the function has an explicit type annotation for a promise, emit the
3454-
// constructor.
3455-
if (promiseConstructor) {
3456-
// If we did not have lexical arguments, supply undefined (void 0) for
3457-
// the `arguments` parameter.
3458-
if (!hasLexicalArguments) {
3459-
write(", void 0");
3460-
}
3461-
3462-
write(", ");
3463-
emitNodeWithoutSourceMap(promiseConstructor);
3464-
}
3465-
34663467
write(")");
34673468

34683469
// If this is not an async arrow, emit the closing brace of the outer function body.

tests/baselines/reference/asyncArrowFunction1_es6.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,5 @@ var foo = async (): Promise<void> => {
44
};
55

66
//// [asyncArrowFunction1_es6.js]
7-
var foo = () => __awaiter(function* () {
8-
},
9-
this, void 0, Promise);
7+
var foo = () => __awaiter([this, Promise], function* () {
8+
});

tests/baselines/reference/asyncArrowFunction6_es6.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,5 @@ var foo = async (a = await): Promise<void> => {
44
}
55

66
//// [asyncArrowFunction6_es6.js]
7-
var foo = (a = yield ) => __awaiter(function* () {
8-
},
9-
this, void 0, Promise);
7+
var foo = (a = yield ) => __awaiter([this, Promise], function* () {
8+
});

tests/baselines/reference/asyncArrowFunction7_es6.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ var bar = async (): Promise<void> => {
77
}
88

99
//// [asyncArrowFunction7_es6.js]
10-
var bar = () => __awaiter(function* () {
10+
var bar = () => __awaiter([this, Promise], function* () {
1111
// 'await' here is an identifier, and not an await expression.
12-
var foo = (a = yield ) => __awaiter(function* () {
13-
},
14-
this, void 0, Promise);
15-
},
16-
this, void 0, Promise);
12+
var foo = (a = yield ) => __awaiter([this, Promise], function* () {
13+
});
14+
});

tests/baselines/reference/asyncArrowFunction8_es6.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ var foo = async (): Promise<void> => {
55
}
66

77
//// [asyncArrowFunction8_es6.js]
8-
var foo = () => __awaiter(function* () {
8+
var foo = () => __awaiter([this, Promise], function* () {
99
var v = { [yield ]: foo };
10-
},
11-
this, void 0, Promise);
10+
});

tests/baselines/reference/asyncArrowFunctionCapturesArguments_es6.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ class C {
1111
class C {
1212
method() {
1313
function other() { }
14-
var fn = () => __awaiter(function* (arguments) { return yield other.apply(this, arguments); },
15-
this, arguments);
14+
var fn = () => __awaiter([this, , arguments], function* (arguments) { return yield other.apply(this, arguments); });
1615
}
1716
}

tests/baselines/reference/asyncArrowFunctionCapturesThis_es6.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ class C {
99
//// [asyncArrowFunctionCapturesThis_es6.js]
1010
class C {
1111
method() {
12-
var fn = () => __awaiter(function* () { return yield this; },
13-
this);
12+
var fn = () => __awaiter([this], function* () { return yield this; });
1413
}
1514
}

0 commit comments

Comments
 (0)