diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 275f7cfe38b..2427c4d31fd 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2710,6 +2710,7 @@ namespace ts { break; case SyntaxKind.ImplementsKeyword: + case SyntaxKind.PromisesKeyword: // An `implements` HeritageClause is TypeScript syntax. transformFlags |= TransformFlags.AssertTypeScript; break; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6db6dac0ee0..55ef6bd7318 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7740,15 +7740,18 @@ namespace ts { result = mappedTypeRelatedTo(source, target, reportErrors); } else { - result = propertiesRelatedTo(source, target, reportErrors); + result = promisesRelatedTo(source, target, reportErrors); if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportErrors); + result = propertiesRelatedTo(source, target, reportErrors); if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportErrors); + result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportErrors); if (result) { - result &= indexTypesRelatedTo(source, originalSource, target, IndexKind.String, reportErrors); + result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportErrors); if (result) { - result &= indexTypesRelatedTo(source, originalSource, target, IndexKind.Number, reportErrors); + result &= indexTypesRelatedTo(source, originalSource, target, IndexKind.String, reportErrors); + if (result) { + result &= indexTypesRelatedTo(source, originalSource, target, IndexKind.Number, reportErrors); + } } } } @@ -7802,6 +7805,10 @@ namespace ts { return Ternary.False; } + function promisesRelatedTo(_source: Type, _target: Type, _reportErrors: boolean): Ternary { + return Ternary.Maybe; + } + function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary { if (relation === identityRelation) { return propertiesIdenticalTo(source, target); @@ -18234,6 +18241,28 @@ namespace ts { } } + function checkPromisesTypesIdentical(node: ClassLikeDeclaration | InterfaceDeclaration, symbol: Symbol) { + if (symbol.declarations.length === 1) { + return; + } + let firstPromisesType: Type; + for (const declaration of symbol.declarations) { + if (declaration.kind === SyntaxKind.ClassDeclaration || declaration.kind === SyntaxKind.InterfaceDeclaration) { + if (!firstPromisesType) { + const firstPromisesTypeNode = getPromisesHeritageClauseElement(declaration); + firstPromisesType = firstPromisesTypeNode && getTypeFromTypeNode(firstPromisesTypeNode); + } + else { + const promisesTypeNode = getPromisesHeritageClauseElement(declaration); + const promisesType = promisesTypeNode && getTypeFromTypeNode(promisesTypeNode); + if (promisesTypeNode && !isTypeIdenticalTo(firstPromisesType, promisesType)) { + error(node.name, Diagnostics.Any_declarations_of_0_that_promise_a_type_must_promise_the_same_type, node.name.text); + } + } + } + } + } + function checkClassExpression(node: ClassExpression): Type { checkClassLikeDeclaration(node); checkNodeDeferred(node); @@ -18272,6 +18301,7 @@ namespace ts { const typeWithThis = getTypeWithThisArgument(type); const staticType = getTypeOfSymbol(symbol); checkTypeParameterListsIdentical(node, symbol); + checkPromisesTypesIdentical(node, symbol); checkClassForDuplicateDeclarations(node); const baseTypeNode = getClassExtendsHeritageClauseElement(node); @@ -18342,6 +18372,26 @@ namespace ts { } } + const promisesTypeNode = getPromisesHeritageClauseElement(node); + if (promisesTypeNode) { + if (!isEntityNameExpression(promisesTypeNode.expression)) { + error(promisesTypeNode.expression, Diagnostics.A_class_or_interface_can_only_promise_an_identifier_Slashqualified_name_with_optional_type_arguments); + } + checkTypeReferenceNode(promisesTypeNode); + if (produceDiagnostics) { + const t = getTypeFromTypeNode(promisesTypeNode); + if (t !== unknownType) { + const promisedType = getPromisedTypeOfPromise(type); + if (!promisedType) { + error(node.name || node, Diagnostics.Class_or_interface_0_incorrectly_promises_type_1_A_compatible_then_signature_could_not_be_found, typeToString(type), typeToString(t)); + } + else { + checkTypeAssignableTo(t, promisedType, node.name || node, Diagnostics.Class_or_interface_promises_type_0_which_is_not_compatible_with_the_type_1_used_by_the_fulfillment_callbacks_of_its_then_members); + } + } + } + } + if (produceDiagnostics) { checkIndexConstraints(type); checkTypeForDuplicateIndexSignatures(node); @@ -18541,6 +18591,14 @@ namespace ts { // Grammar checking checkGrammarDecorators(node) || checkGrammarModifiers(node) || checkGrammarInterfaceDeclaration(node); + const promisesTypeNode = getPromisesHeritageClauseElement(node); + if (promisesTypeNode) { + if (!isEntityNameExpression(promisesTypeNode.expression)) { + error(promisesTypeNode.expression, Diagnostics.A_class_or_interface_can_only_promise_an_identifier_Slashqualified_name_with_optional_type_arguments); + } + checkTypeReferenceNode(promisesTypeNode); + } + checkTypeParameters(node.typeParameters); if (produceDiagnostics) { checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); @@ -18548,6 +18606,7 @@ namespace ts { checkExportsOnMergedDeclarations(node); const symbol = getSymbolOfNode(node); checkTypeParameterListsIdentical(node, symbol); + checkPromisesTypesIdentical(node, symbol); // Only check this symbol once const firstInterfaceDecl = getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration); @@ -18561,6 +18620,19 @@ namespace ts { } checkIndexConstraints(type); } + + if (promisesTypeNode) { + const promisesType = getTypeFromTypeNode(promisesTypeNode); + if (promisesType !== unknownType) { + const promisedType = getPromisedTypeOfPromise(type); + if (!promisedType) { + error(node.name || node, Diagnostics.Class_or_interface_0_incorrectly_promises_type_1_A_compatible_then_signature_could_not_be_found, typeToString(type), typeToString(promisesType)); + } + else { + checkTypeAssignableTo(promisesType, promisedType, node.name || node, Diagnostics.Class_or_interface_promises_type_0_which_is_not_compatible_with_the_type_1_used_by_the_fulfillment_callbacks_of_its_then_members); + } + } + } } checkObjectTypeForDuplicateDeclarations(node); } @@ -21343,31 +21415,46 @@ namespace ts { function checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) { let seenExtendsClause = false; let seenImplementsClause = false; + let seenPromisesClause = false; if (!checkGrammarDecorators(node) && !checkGrammarModifiers(node) && node.heritageClauses) { for (const heritageClause of node.heritageClauses) { - if (heritageClause.token === SyntaxKind.ExtendsKeyword) { - if (seenExtendsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); - } + switch (heritageClause.token) { + case SyntaxKind.ExtendsKeyword: + if (seenExtendsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics._0_clause_already_seen, "extends"); + } + if (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics._0_clause_must_precede_0_clause, "extends", "implements"); + } + if (seenPromisesClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics._0_clause_must_precede_0_clause, "extends", "promises"); + } + if (heritageClause.types.length > 1) { + return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class); + } + seenExtendsClause = true; + break; - if (seenImplementsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_must_precede_implements_clause); - } + case SyntaxKind.ImplementsKeyword: + if (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics._0_clause_already_seen, "implements"); + } + if (seenPromisesClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics._0_clause_must_precede_0_clause, "implements", "promises"); + } + seenImplementsClause = true; + break; - if (heritageClause.types.length > 1) { - return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class); - } - - seenExtendsClause = true; - } - else { - Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); - if (seenImplementsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.implements_clause_already_seen); - } - - seenImplementsClause = true; + case SyntaxKind.PromisesKeyword: + if (seenPromisesClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics._0_clause_already_seen, "promises"); + } + if (heritageClause.types.length > 1) { + return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_and_interfaces_can_only_promise_a_single_type); + } + seenPromisesClause = true; + break; } // Grammar checking heritageClause inside class declaration @@ -21378,19 +21465,31 @@ namespace ts { function checkGrammarInterfaceDeclaration(node: InterfaceDeclaration) { let seenExtendsClause = false; + let seenPromisesClause = false; if (node.heritageClauses) { for (const heritageClause of node.heritageClauses) { - if (heritageClause.token === SyntaxKind.ExtendsKeyword) { - if (seenExtendsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); - } - - seenExtendsClause = true; - } - else { - Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); - return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); + switch (heritageClause.token) { + case SyntaxKind.ExtendsKeyword: + if (seenExtendsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics._0_clause_already_seen, "extends"); + } + if (seenPromisesClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics._0_clause_must_precede_0_clause, "extends", "promises"); + } + seenExtendsClause = true; + break; + case SyntaxKind.ImplementsKeyword: + return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); + case SyntaxKind.PromisesKeyword: + if (seenPromisesClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics._0_clause_already_seen, "promises"); + } + if (heritageClause.types.length > 1) { + return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_and_interfaces_can_only_promise_a_single_type); + } + seenPromisesClause = true; + break; } // Grammar checking heritageClause inside class declaration diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index be1527df1f7..ff732e7d83d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -511,11 +511,11 @@ "category": "Error", "code": 1171 }, - "'extends' clause already seen.": { + "'{0}' clause already seen.": { "category": "Error", "code": 1172 }, - "'extends' clause must precede 'implements' clause.": { + "'{0}' clause must precede '{0}' clause.": { "category": "Error", "code": 1173 }, @@ -523,7 +523,7 @@ "category": "Error", "code": 1174 }, - "'implements' clause already seen.": { + "Classes and interfaces can only promise a single type.": { "category": "Error", "code": 1175 }, @@ -1323,6 +1323,10 @@ "category": "Error", "code": 2420 }, + "Class or interface '{0}' incorrectly promises type '{1}'. A compatible 'then()' signature could not be found.": { + "category": "Error", + "code": 2421 + }, "A class may only implement another class or interface.": { "category": "Error", "code": 2422 @@ -1351,6 +1355,10 @@ "category": "Error", "code": 2428 }, + "Class or interface promises type '{0}' which is not compatible with the type '{1}' used by the fulfillment callbacks of its 'then()' members.": { + "category": "Error", + "code": 2429 + }, "Interface '{0}' incorrectly extends interface '{1}'.": { "category": "Error", "code": 2430 @@ -1427,6 +1435,10 @@ "category": "Error", "code": 2448 }, + "Any declarations of '{0}' that promise a type must promise the same type.": { + "category": "Error", + "code": 2449 + }, "Cannot redeclare block-scoped variable '{0}'.": { "category": "Error", "code": 2451 @@ -1627,6 +1639,10 @@ "category": "Error", "code": 2503 }, + "A class or interface can only promise an identifier/qualified-name with optional type arguments.": { + "category": "Error", + "code": 2504 + }, "A generator cannot have a 'void' type annotation.": { "category": "Error", "code": 2505 diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index ac1f52a991f..22f1c8f022d 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1333,7 +1333,7 @@ namespace ts { // Clauses - export function createHeritageClause(token: SyntaxKind, types: ExpressionWithTypeArguments[], location?: TextRange) { + export function createHeritageClause(token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword | SyntaxKind.PromisesKeyword, types: ExpressionWithTypeArguments[], location?: TextRange) { const node = createNode(SyntaxKind.HeritageClause, location); node.token = token; node.types = createNodeArray(types); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index e214bbd67ca..75a2b0e7de8 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -5412,9 +5412,10 @@ namespace ts { } function parseHeritageClause() { - if (token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword) { + const keyword = token(); + if (keyword === SyntaxKind.ExtendsKeyword || keyword === SyntaxKind.ImplementsKeyword || keyword === SyntaxKind.PromisesKeyword) { const node = createNode(SyntaxKind.HeritageClause); - node.token = token(); + node.token = keyword; nextToken(); node.types = parseDelimitedList(ParsingContext.HeritageClauseElement, parseExpressionWithTypeArguments); return finishNode(node); @@ -5434,7 +5435,13 @@ namespace ts { } function isHeritageClause(): boolean { - return token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; + switch (token()) { + case SyntaxKind.ExtendsKeyword: + case SyntaxKind.ImplementsKeyword: + case SyntaxKind.PromisesKeyword: + return true; + } + return false; } function parseClassMembers() { diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index a2ca6057845..c8ceb9619d2 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -101,6 +101,7 @@ namespace ts { "package": SyntaxKind.PackageKeyword, "private": SyntaxKind.PrivateKeyword, "protected": SyntaxKind.ProtectedKeyword, + "promises": SyntaxKind.PromisesKeyword, "public": SyntaxKind.PublicKeyword, "readonly": SyntaxKind.ReadonlyKeyword, "require": SyntaxKind.RequireKeyword, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 21a394ff40d..c245641924b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -172,6 +172,7 @@ namespace ts { ModuleKeyword, NamespaceKeyword, NeverKeyword, + PromisesKeyword, ReadonlyKeyword, RequireKeyword, NumberKeyword, @@ -1726,7 +1727,7 @@ namespace ts { export interface HeritageClause extends Node { kind: SyntaxKind.HeritageClause; - token: SyntaxKind; + token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword | SyntaxKind.PromisesKeyword; types?: NodeArray; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 192ef888b11..7bf9f0b299f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1806,6 +1806,11 @@ namespace ts { return heritageClause ? heritageClause.types : undefined; } + export function getPromisesHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration) { + const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.PromisesKeyword); + return heritageClause && heritageClause.types.length > 0 ? heritageClause.types[0] : undefined; + } + export function getInterfaceBaseTypeNodes(node: InterfaceDeclaration) { const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword); return heritageClause ? heritageClause.types : undefined;