Skip to content

Commit 763e78d

Browse files
committed
Simplify async return type checking
1 parent 8421f73 commit 763e78d

File tree

7 files changed

+108
-188
lines changed

7 files changed

+108
-188
lines changed

src/compiler/checker.ts

Lines changed: 52 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -5787,90 +5787,6 @@ namespace ts {
57875787
}
57885788
}
57895789

5790-
function getStaticTypeFromTypeNode(node: TypeNode) {
5791-
switch (node.kind) {
5792-
case SyntaxKind.AnyKeyword:
5793-
case SyntaxKind.JSDocAllType:
5794-
case SyntaxKind.JSDocUnknownType:
5795-
return anyType;
5796-
case SyntaxKind.StringKeyword:
5797-
return stringType;
5798-
case SyntaxKind.NumberKeyword:
5799-
return numberType;
5800-
case SyntaxKind.BooleanKeyword:
5801-
return booleanType;
5802-
case SyntaxKind.SymbolKeyword:
5803-
return esSymbolType;
5804-
case SyntaxKind.VoidKeyword:
5805-
return voidType;
5806-
case SyntaxKind.UndefinedKeyword:
5807-
return undefinedType;
5808-
case SyntaxKind.NullKeyword:
5809-
return nullType;
5810-
case SyntaxKind.NeverKeyword:
5811-
return neverType;
5812-
case SyntaxKind.JSDocNullKeyword:
5813-
return nullType;
5814-
case SyntaxKind.JSDocUndefinedKeyword:
5815-
return undefinedType;
5816-
case SyntaxKind.JSDocNeverKeyword:
5817-
return neverType;
5818-
case SyntaxKind.ThisType:
5819-
case SyntaxKind.ThisKeyword:
5820-
return getTypeFromThisTypeNode(node);
5821-
case SyntaxKind.LiteralType:
5822-
return getTypeFromLiteralTypeNode(<LiteralTypeNode>node);
5823-
case SyntaxKind.JSDocLiteralType:
5824-
return getTypeFromLiteralTypeNode((<JSDocLiteralType>node).literal);
5825-
case SyntaxKind.TypeReference:
5826-
case SyntaxKind.JSDocTypeReference:
5827-
return getTypeFromTypeReference(<TypeReferenceNode>node);
5828-
case SyntaxKind.TypePredicate:
5829-
return booleanType;
5830-
case SyntaxKind.ExpressionWithTypeArguments:
5831-
return getTypeFromTypeReference(<ExpressionWithTypeArguments>node);
5832-
case SyntaxKind.TypeQuery:
5833-
return getTypeFromTypeQueryNode(<TypeQueryNode>node);
5834-
case SyntaxKind.ArrayType:
5835-
case SyntaxKind.JSDocArrayType:
5836-
return getTypeFromArrayTypeNode(<ArrayTypeNode>node);
5837-
case SyntaxKind.TupleType:
5838-
return getTypeFromTupleTypeNode(<TupleTypeNode>node);
5839-
case SyntaxKind.UnionType:
5840-
case SyntaxKind.JSDocUnionType:
5841-
return getTypeFromUnionTypeNode(<UnionTypeNode>node, aliasSymbol, aliasTypeArguments);
5842-
case SyntaxKind.IntersectionType:
5843-
return getTypeFromIntersectionTypeNode(<IntersectionTypeNode>node, aliasSymbol, aliasTypeArguments);
5844-
case SyntaxKind.ParenthesizedType:
5845-
case SyntaxKind.JSDocNullableType:
5846-
case SyntaxKind.JSDocNonNullableType:
5847-
case SyntaxKind.JSDocConstructorType:
5848-
case SyntaxKind.JSDocThisType:
5849-
case SyntaxKind.JSDocOptionalType:
5850-
return getTypeFromTypeNode((<ParenthesizedTypeNode | JSDocTypeReferencingNode>node).type);
5851-
case SyntaxKind.JSDocRecordType:
5852-
return getTypeFromTypeNode((node as JSDocRecordType).literal);
5853-
case SyntaxKind.FunctionType:
5854-
case SyntaxKind.ConstructorType:
5855-
case SyntaxKind.TypeLiteral:
5856-
case SyntaxKind.JSDocTypeLiteral:
5857-
case SyntaxKind.JSDocFunctionType:
5858-
return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node, aliasSymbol, aliasTypeArguments);
5859-
// This function assumes that an identifier or qualified name is a type expression
5860-
// Callers should first ensure this by calling isTypeNode
5861-
case SyntaxKind.Identifier:
5862-
case SyntaxKind.QualifiedName:
5863-
const symbol = getSymbolAtLocation(node);
5864-
return symbol && getDeclaredTypeOfSymbol(symbol);
5865-
case SyntaxKind.JSDocTupleType:
5866-
return getTypeFromJSDocTupleType(<JSDocTupleType>node);
5867-
case SyntaxKind.JSDocVariadicType:
5868-
return getTypeFromJSDocVariadicType(<JSDocVariadicType>node);
5869-
default:
5870-
return unknownType;
5871-
}
5872-
}
5873-
58745790
function instantiateList<T>(items: T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T[] {
58755791
if (items && items.length) {
58765792
const result: T[] = [];
@@ -15308,56 +15224,55 @@ namespace ts {
1530815224
// }
1530915225
//
1531015226

15311-
const promiseName = getEntityNameFromTypeNode(node.type);
15312-
const rootName = getFirstIdentifier(promiseName);
15313-
15314-
// Mark the root symbol as referenced.
15315-
getSymbolLinks(rootName.symbol).referenced = true;
15227+
// Always mark the type node as referenced if it points to a value
15228+
markTypeNodeAsReferenced(node.type);
1531615229

15230+
const promiseConstructorName = getEntityNameFromTypeNode(node.type);
1531715231
const promiseType = getTypeFromTypeNode(node.type);
15318-
if (promiseType === unknownType && compilerOptions.isolatedModules) {
15319-
// If we are compiling with isolatedModules, we may not be able to resolve the
15320-
// type as a value. As such, we will just return unknownType.
15232+
if (promiseType === unknownType) {
15233+
if (!compilerOptions.isolatedModules) {
15234+
if (promiseConstructorName) {
15235+
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));
15236+
}
15237+
else {
15238+
error(node.type, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type);
15239+
}
15240+
}
15241+
return unknownType;
15242+
}
15243+
15244+
if (promiseConstructorName === undefined) {
15245+
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));
1532115246
return unknownType;
1532215247
}
1532315248

15324-
const promiseConstructorType = getStaticTypeFromTypeNode(node.type);
15249+
const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true);
15250+
const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : unknownType;
15251+
if (promiseConstructorType === unknownType) {
15252+
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));
15253+
return unknownType;
15254+
}
1532515255

1532615256
const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType();
1532715257
if (globalPromiseConstructorLikeType === emptyObjectType) {
1532815258
// If we couldn't resolve the global PromiseConstructorLike type we cannot verify
1532915259
// compatibility with __awaiter.
15330-
error(node.type || node.name || node, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type);
15260+
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));
1533115261
return unknownType;
1533215262
}
1533315263

15334-
// When we get the type of the `Promise` symbol here, we get the type of the static
15335-
// side of the `Promise` class, which would be `{ new <T>(...): Promise<T> }`.
15336-
15337-
const promiseConstructor = getNodeLinks(node.type).resolvedSymbol;
15338-
if (!promiseConstructor) {
15339-
// try to fall back to global promise type.
15340-
const typeName = promiseConstructor
15341-
? symbolToString(promiseConstructor)
15342-
: typeToString(promiseType);
15343-
return checkCorrectPromiseType(promiseType, node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type, typeName);
15344-
}
15345-
15346-
// If the Promise constructor, resolved locally, is an alias symbol we should mark it as referenced.
15347-
checkReturnTypeAnnotationAsExpression(node);
15348-
15349-
// Validate the promise constructor type.
15350-
const promiseConstructorType = getTypeOfSymbol(promiseConstructor);
15351-
if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type)) {
15264+
if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, node.type,
15265+
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)) {
1535215266
return unknownType;
1535315267
}
1535415268

1535515269
// Verify there is no local declaration that could collide with the promise constructor.
15356-
const rootSymbol = getSymbol(node.locals, rootName.text, SymbolFlags.Value);
15357-
if (rootSymbol) {
15358-
error(rootSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions,
15270+
const rootName = promiseConstructorName && getFirstIdentifier(promiseConstructorName);
15271+
const collidingSymbol = getSymbol(node.locals, rootName.text, SymbolFlags.Value);
15272+
if (collidingSymbol) {
15273+
error(collidingSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions,
1535915274
rootName.text,
15360-
getFullyQualifiedName(promiseConstructor));
15275+
entityNameToString(promiseConstructorName));
1536115276
return unknownType;
1536215277
}
1536315278

@@ -15415,44 +15330,19 @@ namespace ts {
1541515330
errorInfo);
1541615331
}
1541715332

15418-
/** Checks a type reference node as an expression. */
15419-
function checkTypeNodeAsExpression(node: TypeNode) {
15420-
// When we are emitting type metadata for decorators, we need to try to check the type
15421-
// as if it were an expression so that we can emit the type in a value position when we
15422-
// serialize the type metadata.
15423-
if (node && node.kind === SyntaxKind.TypeReference) {
15424-
const root = getFirstIdentifier((<TypeReferenceNode>node).typeName);
15425-
const meaning = root.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace;
15426-
// Resolve type so we know which symbol is referenced
15427-
const rootSymbol = resolveName(root, root.text, meaning | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined);
15428-
// Resolved symbol is alias
15429-
if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) {
15430-
const aliasTarget = resolveAlias(rootSymbol);
15431-
// If alias has value symbol - mark alias as referenced
15432-
if (aliasTarget.flags & SymbolFlags.Value && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol))) {
15433-
markAliasSymbolAsReferenced(rootSymbol);
15434-
}
15435-
}
15436-
}
15437-
}
15438-
1543915333
/**
15440-
* Checks the type annotation of an accessor declaration or property declaration as
15441-
* an expression if it is a type reference to a type with a value declaration.
15442-
*/
15443-
function checkTypeAnnotationAsExpression(node: VariableLikeDeclaration) {
15444-
checkTypeNodeAsExpression((<PropertyDeclaration>node).type);
15445-
}
15446-
15447-
function checkReturnTypeAnnotationAsExpression(node: FunctionLikeDeclaration) {
15448-
checkTypeNodeAsExpression(node.type);
15449-
}
15450-
15451-
/** Checks the type annotation of the parameters of a function/method or the constructor of a class as expressions */
15452-
function checkParameterTypeAnnotationsAsExpressions(node: FunctionLikeDeclaration) {
15453-
// ensure all type annotations with a value declaration are checked as an expression
15454-
for (const parameter of node.parameters) {
15455-
checkTypeAnnotationAsExpression(parameter);
15334+
* If a TypeNode can be resolved to a value symbol imported from an external module, it is
15335+
* marked as referenced to prevent import elision.
15336+
*/
15337+
function markTypeNodeAsReferenced(node: TypeNode) {
15338+
const typeName = node && getEntityNameFromTypeNode(node);
15339+
const rootName = typeName && getFirstIdentifier(typeName);
15340+
const rootSymbol = rootName && resolveName(rootName, rootName.text, (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined);
15341+
if (rootSymbol
15342+
&& rootSymbol.flags & SymbolFlags.Alias
15343+
&& symbolIsValue(rootSymbol)
15344+
&& !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol))) {
15345+
markAliasSymbolAsReferenced(rootSymbol);
1545615346
}
1545715347
}
1545815348

@@ -15478,20 +15368,25 @@ namespace ts {
1547815368
case SyntaxKind.ClassDeclaration:
1547915369
const constructor = getFirstConstructorWithBody(<ClassDeclaration>node);
1548015370
if (constructor) {
15481-
checkParameterTypeAnnotationsAsExpressions(constructor);
15371+
for (const parameter of constructor.parameters) {
15372+
markTypeNodeAsReferenced(parameter.type);
15373+
}
1548215374
}
1548315375
break;
1548415376

1548515377
case SyntaxKind.MethodDeclaration:
1548615378
case SyntaxKind.GetAccessor:
1548715379
case SyntaxKind.SetAccessor:
15488-
checkParameterTypeAnnotationsAsExpressions(<FunctionLikeDeclaration>node);
15489-
checkReturnTypeAnnotationAsExpression(<FunctionLikeDeclaration>node);
15380+
for (const parameter of (<FunctionLikeDeclaration>node).parameters) {
15381+
markTypeNodeAsReferenced(parameter.type);
15382+
}
15383+
15384+
markTypeNodeAsReferenced((<FunctionLikeDeclaration>node).type);
1549015385
break;
1549115386

1549215387
case SyntaxKind.PropertyDeclaration:
1549315388
case SyntaxKind.Parameter:
15494-
checkTypeAnnotationAsExpression(<PropertyDeclaration | ParameterDeclaration>node);
15389+
markTypeNodeAsReferenced((<PropertyDeclaration | ParameterDeclaration>node).type);
1549515390
break;
1549615391
}
1549715392
}

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/ts.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2329,15 +2329,16 @@ namespace ts {
23292329
}
23302330

23312331
function getPromiseConstructor(type: TypeNode) {
2332-
const typeName = getEntityNameFromTypeNode(type);
2333-
if (typeName && isEntityName(typeName)) {
2334-
const serializationKind = resolver.getTypeReferenceSerializationKind(typeName);
2335-
if (serializationKind === TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue
2336-
|| serializationKind === TypeReferenceSerializationKind.Unknown) {
2337-
return typeName;
2332+
if (type) {
2333+
const typeName = getEntityNameFromTypeNode(type);
2334+
if (typeName && isEntityName(typeName)) {
2335+
const serializationKind = resolver.getTypeReferenceSerializationKind(typeName);
2336+
if (serializationKind === TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue
2337+
|| serializationKind === TypeReferenceSerializationKind.Unknown) {
2338+
return typeName;
2339+
}
23382340
}
23392341
}
2340-
23412342
return undefined;
23422343
}
23432344

src/compiler/utilities.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,17 @@ namespace ts {
503503
return getFullWidth(name) === 0 ? "(Missing)" : getTextOfNode(name);
504504
}
505505

506+
export function entityNameToString(name: EntityNameOrEntityNameExpression): string {
507+
switch (name.kind) {
508+
case SyntaxKind.Identifier:
509+
return getFullWidth(name) === 0 ? unescapeIdentifier((<Identifier>name).text) : getTextOfNode(name);
510+
case SyntaxKind.QualifiedName:
511+
return entityNameToString((<QualifiedName>name).left) + "." + entityNameToString((<QualifiedName>name).right);
512+
case SyntaxKind.PropertyAccessExpression:
513+
return entityNameToString((<PropertyAccessEntityNameExpression>name).expression) + "." + entityNameToString((<PropertyAccessEntityNameExpression>name).name);
514+
}
515+
}
516+
506517
export function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): Diagnostic {
507518
const sourceFile = getSourceFileOfNode(node);
508519
const span = getErrorSpanForNode(sourceFile, node);
@@ -1054,17 +1065,19 @@ namespace ts {
10541065
}
10551066

10561067
export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNameExpression {
1057-
if (node) {
1058-
switch (node.kind) {
1059-
case SyntaxKind.TypeReference:
1060-
return (<TypeReferenceNode>node).typeName;
1061-
case SyntaxKind.ExpressionWithTypeArguments:
1062-
Debug.assert(isEntityNameExpression((<ExpressionWithTypeArguments>node).expression));
1063-
return <EntityNameExpression>(<ExpressionWithTypeArguments>node).expression;
1064-
case SyntaxKind.Identifier:
1065-
case SyntaxKind.QualifiedName:
1066-
return (<EntityName><Node>node);
1067-
}
1068+
switch (node.kind) {
1069+
case SyntaxKind.TypeReference:
1070+
case SyntaxKind.JSDocTypeReference:
1071+
return (<TypeReferenceNode>node).typeName;
1072+
1073+
case SyntaxKind.ExpressionWithTypeArguments:
1074+
return isEntityNameExpression((<ExpressionWithTypeArguments>node).expression)
1075+
? <EntityNameExpression>(<ExpressionWithTypeArguments>node).expression
1076+
: undefined;
1077+
1078+
case SyntaxKind.Identifier:
1079+
case SyntaxKind.QualifiedName:
1080+
return (<EntityName><Node>node);
10681081
}
10691082

10701083
return undefined;

0 commit comments

Comments
 (0)