Skip to content

Commit 4371889

Browse files
authored
Merge pull request #11430 from Microsoft/fixAsyncReturnTypeCheck
Fix async return type check
2 parents ecb7b00 + 884e9f4 commit 4371889

8 files changed

+179
-121
lines changed

src/compiler/checker.ts

Lines changed: 96 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -15546,31 +15546,24 @@ namespace ts {
1554615546
}
1554715547

1554815548
/**
15549-
* Checks the return type of an async function to ensure it is a compatible
15550-
* Promise implementation.
15551-
* @param node The signature to check
15552-
* @param returnType The return type for the function
15553-
* @remarks
15554-
* This checks that an async function has a valid Promise-compatible return type,
15555-
* and returns the *awaited type* of the promise. An async function has a valid
15556-
* Promise-compatible return type if the resolved value of the return type has a
15557-
* construct signature that takes in an `initializer` function that in turn supplies
15558-
* a `resolve` function as one of its arguments and results in an object with a
15559-
* callable `then` signature.
15560-
*/
15549+
* Checks the return type of an async function to ensure it is a compatible
15550+
* Promise implementation.
15551+
*
15552+
* This checks that an async function has a valid Promise-compatible return type,
15553+
* and returns the *awaited type* of the promise. An async function has a valid
15554+
* Promise-compatible return type if the resolved value of the return type has a
15555+
* construct signature that takes in an `initializer` function that in turn supplies
15556+
* a `resolve` function as one of its arguments and results in an object with a
15557+
* callable `then` signature.
15558+
*
15559+
* @param node The signature to check
15560+
*/
1556115561
function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration): Type {
1556215562
if (languageVersion >= ScriptTarget.ES2015) {
1556315563
const returnType = getTypeFromTypeNode(node.type);
1556415564
return checkCorrectPromiseType(returnType, node.type, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type);
1556515565
}
1556615566

15567-
const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType();
15568-
if (globalPromiseConstructorLikeType === emptyObjectType) {
15569-
// If we couldn't resolve the global PromiseConstructorLike type we cannot verify
15570-
// compatibility with __awaiter.
15571-
return unknownType;
15572-
}
15573-
1557415567
// As part of our emit for an async function, we will need to emit the entity name of
1557515568
// the return type annotation as an expression. To meet the necessary runtime semantics
1557615569
// for __awaiter, we must also check that the type of the declaration (e.g. the static
@@ -15595,42 +15588,56 @@ namespace ts {
1559515588
// then<U>(...): Promise<U>;
1559615589
// }
1559715590
//
15598-
// When we get the type of the `Promise` symbol here, we get the type of the static
15599-
// side of the `Promise` class, which would be `{ new <T>(...): Promise<T> }`.
1560015591

15592+
// Always mark the type node as referenced if it points to a value
15593+
markTypeNodeAsReferenced(node.type);
15594+
15595+
const promiseConstructorName = getEntityNameFromTypeNode(node.type);
1560115596
const promiseType = getTypeFromTypeNode(node.type);
15602-
if (promiseType === unknownType && compilerOptions.isolatedModules) {
15603-
// If we are compiling with isolatedModules, we may not be able to resolve the
15604-
// type as a value. As such, we will just return unknownType;
15597+
if (promiseType === unknownType) {
15598+
if (!compilerOptions.isolatedModules) {
15599+
if (promiseConstructorName) {
15600+
error(node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName));
15601+
}
15602+
else {
15603+
error(node.type, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type);
15604+
}
15605+
}
1560515606
return unknownType;
1560615607
}
1560715608

15608-
const promiseConstructor = getNodeLinks(node.type).resolvedSymbol;
15609-
if (!promiseConstructor || !symbolIsValue(promiseConstructor)) {
15610-
// try to fall back to global promise type.
15611-
const typeName = promiseConstructor
15612-
? symbolToString(promiseConstructor)
15613-
: typeToString(promiseType);
15614-
return checkCorrectPromiseType(promiseType, node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type, typeName);
15609+
if (promiseConstructorName === undefined) {
15610+
error(node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(promiseType));
15611+
return unknownType;
1561515612
}
1561615613

15617-
// If the Promise constructor, resolved locally, is an alias symbol we should mark it as referenced.
15618-
checkReturnTypeAnnotationAsExpression(node);
15614+
const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true);
15615+
const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : unknownType;
15616+
if (promiseConstructorType === unknownType) {
15617+
error(node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName));
15618+
return unknownType;
15619+
}
1561915620

15620-
// Validate the promise constructor type.
15621-
const promiseConstructorType = getTypeOfSymbol(promiseConstructor);
15622-
if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type)) {
15621+
const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType();
15622+
if (globalPromiseConstructorLikeType === emptyObjectType) {
15623+
// If we couldn't resolve the global PromiseConstructorLike type we cannot verify
15624+
// compatibility with __awaiter.
15625+
error(node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName));
15626+
return unknownType;
15627+
}
15628+
15629+
if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, node.type,
15630+
Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value)) {
1562315631
return unknownType;
1562415632
}
1562515633

1562615634
// Verify there is no local declaration that could collide with the promise constructor.
15627-
const promiseName = getEntityNameFromTypeNode(node.type);
15628-
const promiseNameOrNamespaceRoot = getFirstIdentifier(promiseName);
15629-
const rootSymbol = getSymbol(node.locals, promiseNameOrNamespaceRoot.text, SymbolFlags.Value);
15630-
if (rootSymbol) {
15631-
error(rootSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions,
15632-
promiseNameOrNamespaceRoot.text,
15633-
getFullyQualifiedName(promiseConstructor));
15635+
const rootName = promiseConstructorName && getFirstIdentifier(promiseConstructorName);
15636+
const collidingSymbol = getSymbol(node.locals, rootName.text, SymbolFlags.Value);
15637+
if (collidingSymbol) {
15638+
error(collidingSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions,
15639+
rootName.text,
15640+
entityNameToString(promiseConstructorName));
1563415641
return unknownType;
1563515642
}
1563615643

@@ -15688,44 +15695,19 @@ namespace ts {
1568815695
errorInfo);
1568915696
}
1569015697

15691-
/** Checks a type reference node as an expression. */
15692-
function checkTypeNodeAsExpression(node: TypeNode) {
15693-
// When we are emitting type metadata for decorators, we need to try to check the type
15694-
// as if it were an expression so that we can emit the type in a value position when we
15695-
// serialize the type metadata.
15696-
if (node && node.kind === SyntaxKind.TypeReference) {
15697-
const root = getFirstIdentifier((<TypeReferenceNode>node).typeName);
15698-
const meaning = root.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace;
15699-
// Resolve type so we know which symbol is referenced
15700-
const rootSymbol = resolveName(root, root.text, meaning | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined);
15701-
// Resolved symbol is alias
15702-
if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) {
15703-
const aliasTarget = resolveAlias(rootSymbol);
15704-
// If alias has value symbol - mark alias as referenced
15705-
if (aliasTarget.flags & SymbolFlags.Value && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol))) {
15706-
markAliasSymbolAsReferenced(rootSymbol);
15707-
}
15708-
}
15709-
}
15710-
}
15711-
1571215698
/**
15713-
* Checks the type annotation of an accessor declaration or property declaration as
15714-
* an expression if it is a type reference to a type with a value declaration.
15715-
*/
15716-
function checkTypeAnnotationAsExpression(node: VariableLikeDeclaration) {
15717-
checkTypeNodeAsExpression((<PropertyDeclaration>node).type);
15718-
}
15719-
15720-
function checkReturnTypeAnnotationAsExpression(node: FunctionLikeDeclaration) {
15721-
checkTypeNodeAsExpression(node.type);
15722-
}
15723-
15724-
/** Checks the type annotation of the parameters of a function/method or the constructor of a class as expressions */
15725-
function checkParameterTypeAnnotationsAsExpressions(node: FunctionLikeDeclaration) {
15726-
// ensure all type annotations with a value declaration are checked as an expression
15727-
for (const parameter of node.parameters) {
15728-
checkTypeAnnotationAsExpression(parameter);
15699+
* If a TypeNode can be resolved to a value symbol imported from an external module, it is
15700+
* marked as referenced to prevent import elision.
15701+
*/
15702+
function markTypeNodeAsReferenced(node: TypeNode) {
15703+
const typeName = node && getEntityNameFromTypeNode(node);
15704+
const rootName = typeName && getFirstIdentifier(typeName);
15705+
const rootSymbol = rootName && resolveName(rootName, rootName.text, (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined);
15706+
if (rootSymbol
15707+
&& rootSymbol.flags & SymbolFlags.Alias
15708+
&& symbolIsValue(rootSymbol)
15709+
&& !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol))) {
15710+
markAliasSymbolAsReferenced(rootSymbol);
1572915711
}
1573015712
}
1573115713

@@ -15751,20 +15733,25 @@ namespace ts {
1575115733
case SyntaxKind.ClassDeclaration:
1575215734
const constructor = getFirstConstructorWithBody(<ClassDeclaration>node);
1575315735
if (constructor) {
15754-
checkParameterTypeAnnotationsAsExpressions(constructor);
15736+
for (const parameter of constructor.parameters) {
15737+
markTypeNodeAsReferenced(parameter.type);
15738+
}
1575515739
}
1575615740
break;
1575715741

1575815742
case SyntaxKind.MethodDeclaration:
1575915743
case SyntaxKind.GetAccessor:
1576015744
case SyntaxKind.SetAccessor:
15761-
checkParameterTypeAnnotationsAsExpressions(<FunctionLikeDeclaration>node);
15762-
checkReturnTypeAnnotationAsExpression(<FunctionLikeDeclaration>node);
15745+
for (const parameter of (<FunctionLikeDeclaration>node).parameters) {
15746+
markTypeNodeAsReferenced(parameter.type);
15747+
}
15748+
15749+
markTypeNodeAsReferenced((<FunctionLikeDeclaration>node).type);
1576315750
break;
1576415751

1576515752
case SyntaxKind.PropertyDeclaration:
1576615753
case SyntaxKind.Parameter:
15767-
checkTypeAnnotationAsExpression(<PropertyDeclaration | ParameterDeclaration>node);
15754+
markTypeNodeAsReferenced((<PropertyDeclaration | ParameterDeclaration>node).type);
1576815755
break;
1576915756
}
1577015757
}
@@ -18476,9 +18463,33 @@ namespace ts {
1847618463
function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] {
1847718464
throwIfNonDiagnosticsProducing();
1847818465
if (sourceFile) {
18466+
// Some global diagnostics are deferred until they are needed and
18467+
// may not be reported in the firt call to getGlobalDiagnostics.
18468+
// We should catch these changes and report them.
18469+
const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics();
18470+
const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length;
18471+
1847918472
checkSourceFile(sourceFile);
18480-
return diagnostics.getDiagnostics(sourceFile.fileName);
18473+
18474+
const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName);
18475+
const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics();
18476+
if (currentGlobalDiagnostics !== previousGlobalDiagnostics) {
18477+
// If the arrays are not the same reference, new diagnostics were added.
18478+
const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics);
18479+
return concatenate(deferredGlobalDiagnostics, semanticDiagnostics);
18480+
}
18481+
else if (previousGlobalDiagnosticsSize === 0 && currentGlobalDiagnostics.length > 0) {
18482+
// If the arrays are the same reference, but the length has changed, a single
18483+
// new diagnostic was added as DiagnosticCollection attempts to reuse the
18484+
// same array.
18485+
return concatenate(currentGlobalDiagnostics, semanticDiagnostics);
18486+
}
18487+
18488+
return semanticDiagnostics;
1848118489
}
18490+
18491+
// Global diagnostics are always added when a file is not provided to
18492+
// getDiagnostics
1848218493
forEach(host.getSourceFiles(), checkSourceFile);
1848318494
return diagnostics.getDiagnostics();
1848418495
}

src/compiler/core.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -446,8 +446,8 @@ namespace ts {
446446
}
447447

448448
export function concatenate<T>(array1: T[], array2: T[]): T[] {
449-
if (!array2 || !array2.length) return array1;
450-
if (!array1 || !array1.length) return array2;
449+
if (!some(array2)) return array1;
450+
if (!some(array1)) return array2;
451451
return [...array1, ...array2];
452452
}
453453

@@ -527,6 +527,27 @@ namespace ts {
527527
return result || array;
528528
}
529529

530+
/**
531+
* Gets the relative complement of `arrayA` with respect to `b`, returning the elements that
532+
* are not present in `arrayA` but are present in `arrayB`. Assumes both arrays are sorted
533+
* based on the provided comparer.
534+
*/
535+
export function relativeComplement<T>(arrayA: T[] | undefined, arrayB: T[] | undefined, comparer: (x: T, y: T) => Comparison = compareValues, offsetA = 0, offsetB = 0): T[] | undefined {
536+
if (!arrayB || !arrayA || arrayB.length === 0 || arrayA.length === 0) return arrayB;
537+
const result: T[] = [];
538+
outer: for (; offsetB < arrayB.length; offsetB++) {
539+
inner: for (; offsetA < arrayA.length; offsetA++) {
540+
switch (comparer(arrayB[offsetB], arrayA[offsetA])) {
541+
case Comparison.LessThan: break inner;
542+
case Comparison.EqualTo: continue outer;
543+
case Comparison.GreaterThan: continue inner;
544+
}
545+
}
546+
result.push(arrayB[offsetB]);
547+
}
548+
return result;
549+
}
550+
530551
export function sum(array: any[], prop: string): number {
531552
let result = 0;
532553
for (const v of array) {
@@ -626,12 +647,12 @@ namespace ts {
626647
* @param array A sorted array whose first element must be no larger than number
627648
* @param number The value to be searched for in the array.
628649
*/
629-
export function binarySearch<T>(array: T[], value: T, comparer?: (v1: T, v2: T) => number): number {
650+
export function binarySearch<T>(array: T[], value: T, comparer?: (v1: T, v2: T) => number, offset?: number): number {
630651
if (!array || array.length === 0) {
631652
return -1;
632653
}
633654

634-
let low = 0;
655+
let low = offset || 0;
635656
let high = array.length - 1;
636657
comparer = comparer !== undefined
637658
? comparer

src/compiler/diagnosticMessages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@
163163
"category": "Error",
164164
"code": 1054
165165
},
166-
"Type '{0}' is not a valid async function return type.": {
166+
"Type '{0}' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.": {
167167
"category": "Error",
168168
"code": 1055
169169
},

src/compiler/transformers/es2017.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ namespace ts {
262262
}
263263

264264
function transformAsyncFunctionBody(node: FunctionLikeDeclaration): ConciseBody | FunctionBody {
265-
const nodeType = node.original ? (<FunctionLikeDeclaration>node.original).type : node.type;
265+
const original = getOriginalNode(node, isFunctionLike);
266+
const nodeType = original.type;
266267
const promiseConstructor = languageVersion < ScriptTarget.ES2015 ? getPromiseConstructor(nodeType) : undefined;
267268
const isArrowFunction = node.kind === SyntaxKind.ArrowFunction;
268269
const hasLexicalArguments = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.CaptureArguments) !== 0;
@@ -336,15 +337,16 @@ namespace ts {
336337
}
337338

338339
function getPromiseConstructor(type: TypeNode) {
339-
const typeName = getEntityNameFromTypeNode(type);
340-
if (typeName && isEntityName(typeName)) {
341-
const serializationKind = resolver.getTypeReferenceSerializationKind(typeName);
342-
if (serializationKind === TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue
343-
|| serializationKind === TypeReferenceSerializationKind.Unknown) {
344-
return typeName;
340+
if (type) {
341+
const typeName = getEntityNameFromTypeNode(type);
342+
if (typeName && isEntityName(typeName)) {
343+
const serializationKind = resolver.getTypeReferenceSerializationKind(typeName);
344+
if (serializationKind === TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue
345+
|| serializationKind === TypeReferenceSerializationKind.Unknown) {
346+
return typeName;
347+
}
345348
}
346349
}
347-
348350
return undefined;
349351
}
350352

0 commit comments

Comments
 (0)