From 822117050f6e98b2e3a461bde99a0d0b96230f35 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Sun, 6 Sep 2020 15:35:27 +0800 Subject: [PATCH 1/4] feat: introduct throw type --- src/compiler/checker.ts | 24 ++++++++++++++++++++++++ src/compiler/diagnosticMessages.json | 5 ++++- src/compiler/factory/nodeFactory.ts | 2 +- src/compiler/parser.ts | 3 ++- src/compiler/types.ts | 9 +++++++-- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cc4bac59b19f3..bcd661926a94e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4493,6 +4493,9 @@ namespace ts { if (type.flags & TypeFlags.Substitution) { return typeToTypeNodeHelper((type).baseType, context); } + if (type.flags & TypeFlags.ThrowType) { + return typeToTypeNodeHelper(errorType, context); + } return Debug.fail("Should be unreachable."); @@ -13391,6 +13394,9 @@ namespace ts { case SyntaxKind.ReadonlyKeyword: links.resolvedType = getTypeFromTypeNode(node.type); break; + case SyntaxKind.ThrowKeyword: + links.resolvedType = createThrowType(getTypeFromTypeNode(node.type)); + break; default: throw Debug.assertNever(node.operator); } @@ -14461,6 +14467,12 @@ namespace ts { return type; } + function createThrowType(containingType: Type) { + const type = createType(TypeFlags.ThrowType); + type.value = containingType; + return type; + } + function getESSymbolLikeTypeForNode(node: Node) { if (isValidESSymbolDeclaration(node)) { const symbol = getSymbolOfNode(node); @@ -15113,6 +15125,15 @@ namespace ts { return sub; } } + if (flags & TypeFlags.ThrowType) { + const innerType = instantiateType((type).value, mapper); + let errorMessage = "Unknown"; + if (innerType.flags & TypeFlags.StringLiteral) { + errorMessage = (innerType).value; + } + error(currentNode, Diagnostics.Type_instantiated_results_in_a_throw_type_saying_Colon_0, errorMessage); + return errorType; + } return type; } @@ -19312,6 +19333,9 @@ namespace ts { // results for union and intersection types for performance reasons. function couldContainTypeVariables(type: Type): boolean { const objectFlags = getObjectFlags(type); + if (type.flags & TypeFlags.ThrowType) { + return couldContainTypeVariables((type).value); + } if (objectFlags & ObjectFlags.CouldContainTypeVariablesComputed) { return !!(objectFlags & ObjectFlags.CouldContainTypeVariables); } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index eadc4af4c02de..bfe7d8e80c41b 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3020,7 +3020,10 @@ "category": "Error", "code": 2793 }, - + "Type instantiated results in a throw type saying: {0}": { + "category": "Error", + "code": 2794 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 52a9cb70d2240..66a1eed3da6f9 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -1969,7 +1969,7 @@ namespace ts { } // @api - function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode { + function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | SyntaxKind.ThrowKeyword, type: TypeNode): TypeOperatorNode { const node = createBaseNode(SyntaxKind.TypeOperator); node.operator = operator; node.type = parenthesizerRules().parenthesizeMemberOfElementType(type); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index c9259a7abccda..bca7d0ed7a19c 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3585,7 +3585,7 @@ namespace ts { return type; } - function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword) { + function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | SyntaxKind.ThrowKeyword) { const pos = getNodePos(); parseExpected(operator); return finishNode(factory.createTypeOperatorNode(operator, parseTypeOperatorOrHigher()), pos); @@ -3615,6 +3615,7 @@ namespace ts { case SyntaxKind.KeyOfKeyword: case SyntaxKind.UniqueKeyword: case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.ThrowKeyword: return parseTypeOperator(operator); case SyntaxKind.InferKeyword: return parseInferType(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5bfc663a9b124..4e817c203a2ab 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1613,7 +1613,7 @@ namespace ts { export interface TypeOperatorNode extends TypeNode { readonly kind: SyntaxKind.TypeOperator; - readonly operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword; + readonly operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | SyntaxKind.ThrowKeyword; readonly type: TypeNode; } @@ -4873,6 +4873,7 @@ namespace ts { Substitution = 1 << 25, // Type parameter substitution NonPrimitive = 1 << 26, // intrinsic object type TemplateLiteral = 1 << 27, // Template literal type + ThrowType = 1 << 28, // throw T /* @internal */ AnyOrUnknown = Any | Unknown, @@ -5008,6 +5009,10 @@ namespace ts { export interface EnumType extends Type { } + export interface ThrowType extends Type { + value: Type; + } + export const enum ObjectFlags { Class = 1 << 0, // Class Interface = 1 << 1, // Interface @@ -6781,7 +6786,7 @@ namespace ts { createParenthesizedType(type: TypeNode): ParenthesizedTypeNode; updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode): ParenthesizedTypeNode; createThisTypeNode(): ThisTypeNode; - createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode; + createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | SyntaxKind.ThrowKeyword, type: TypeNode): TypeOperatorNode; updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode): TypeOperatorNode; createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode; updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode; From 9f1beab615530321406a960bc856ca15bea358a6 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Sun, 6 Sep 2020 16:00:28 +0800 Subject: [PATCH 2/4] feat: handle throw type in call expression --- src/compiler/checker.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bcd661926a94e..0bd1263a381a0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1053,6 +1053,17 @@ namespace ts { return diagnostic; } + function errorByThrowType(location: Node | undefined, value: Type) { + let message = "Unknown"; + if (value.flags & TypeFlags.ThrowType) { + value = (value).value; + } + if (value.flags & TypeFlags.StringLiteral) { + message = (value).value; + } + error(location, Diagnostics.Type_instantiated_results_in_a_throw_type_saying_Colon_0, message); + } + function addErrorOrSuggestion(isError: boolean, diagnostic: DiagnosticWithLocation) { if (isError) { diagnostics.add(diagnostic); @@ -4494,7 +4505,7 @@ namespace ts { return typeToTypeNodeHelper((type).baseType, context); } if (type.flags & TypeFlags.ThrowType) { - return typeToTypeNodeHelper(errorType, context); + return typeToTypeNodeHelper(neverType, context); } return Debug.fail("Should be unreachable."); @@ -15126,12 +15137,7 @@ namespace ts { } } if (flags & TypeFlags.ThrowType) { - const innerType = instantiateType((type).value, mapper); - let errorMessage = "Unknown"; - if (innerType.flags & TypeFlags.StringLiteral) { - errorMessage = (innerType).value; - } - error(currentNode, Diagnostics.Type_instantiated_results_in_a_throw_type_saying_Colon_0, errorMessage); + errorByThrowType(currentNode, instantiateType((type).value, mapper)); return errorType; } return type; @@ -27798,6 +27804,9 @@ namespace ts { } const returnType = getReturnTypeOfSignature(signature); + if (returnType.flags & TypeFlags.ThrowType) { + errorByThrowType(node, returnType); + } // Treat any call to the global 'Symbol' function that is part of a const variable or readonly property // as a fresh unique symbol literal type. if (returnType.flags & TypeFlags.ESSymbolLike && isSymbolOrSymbolForCall(node)) { From bb2081f2794c522f93163234651ebb512931c7ca Mon Sep 17 00:00:00 2001 From: Jack Works Date: Sun, 6 Sep 2020 16:59:01 +0800 Subject: [PATCH 3/4] feat: add typeof modifier in template string --- src/compiler/checker.ts | 18 +++++++++++++----- src/compiler/emitter.ts | 1 + src/compiler/parser.ts | 1 + src/compiler/types.ts | 1 + 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0bd1263a381a0..71b563720a8c9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13439,12 +13439,15 @@ namespace ts { let text = texts[0]; for (let i = 0; i < types.length; i++) { const t = types[i]; - if (t.flags & TypeFlags.Literal) { - const s = applyTemplateCasing(getTemplateStringForType(t) || "", casings[i]); + const casingType = casings[i]; + const isGeneric = isGenericIndexType(t); + const resolvable = (t.flags & TypeFlags.Literal) || (!isGeneric && casingType === TemplateCasing.TypeOf); + if (resolvable) { + const s = applyTemplateCasing(getTemplateStringForType(t, casingType) || "", casingType); text += s; text += texts[i + 1]; } - else if (isGenericIndexType(t)) { + else if (isGeneric) { newTypes.push(t); newCasings.push(casings[i]); newTexts.push(text); @@ -13466,7 +13469,10 @@ namespace ts { return type; } - function getTemplateStringForType(type: Type) { + function getTemplateStringForType(type: Type, casing: TemplateCasing) { + if (casing === TemplateCasing.TypeOf) { + return getTypeNameForErrorDisplay(type); + } return type.flags & TypeFlags.StringLiteral ? (type).value : type.flags & TypeFlags.NumberLiteral ? "" + (type).value : type.flags & TypeFlags.BigIntLiteral ? pseudoBigIntToString((type).value) : @@ -31424,7 +31430,9 @@ namespace ts { getTypeFromTypeNode(node); for (const span of node.templateSpans) { const type = getTypeFromTypeNode(span.type); - checkTypeAssignableTo(type, templateConstraintType, span.type); + if (span.casing !== TemplateCasing.TypeOf) { + checkTypeAssignableTo(type, templateConstraintType, span.type); + } if (!everyType(type, t => !!(t.flags & TypeFlags.Literal) || isGenericIndexType(t))) { error(span.type, Diagnostics.Template_type_argument_0_is_not_literal_type_or_a_generic_type, typeToString(type)); } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 1255ad200fbb8..5627697e34b70 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2013,6 +2013,7 @@ namespace ts { node.casing === TemplateCasing.Lowercase ? "lowercase" : node.casing === TemplateCasing.Capitalize ? "capitalize" : node.casing === TemplateCasing.Uncapitalize ? "uncapitalize" : + node.casing === TemplateCasing.TypeOf ? "typeof" : undefined; if (keyword) { writeKeyword(keyword); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index bca7d0ed7a19c..44bc0ec33ac22 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2621,6 +2621,7 @@ namespace ts { parseOptional(SyntaxKind.LowercaseKeyword) ? TemplateCasing.Lowercase : parseOptional(SyntaxKind.CapitalizeKeyword) ? TemplateCasing.Capitalize : parseOptional(SyntaxKind.UncapitalizeKeyword) ? TemplateCasing.Uncapitalize : + parseOptional(SyntaxKind.TypeOfKeyword) ? TemplateCasing.TypeOf : TemplateCasing.None; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4e817c203a2ab..796545024e851 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1672,6 +1672,7 @@ namespace ts { Lowercase, Capitalize, Uncapitalize, + TypeOf, } // Note: 'brands' in our syntax nodes serve to give us a small amount of nominal typing. From f1b195df4e300127caaa2d22f65faa202b0a7a6d Mon Sep 17 00:00:00 2001 From: Jack Works Date: Tue, 8 Sep 2020 10:07:39 +0800 Subject: [PATCH 4/4] chore: accept baseline --- tests/baselines/reference/api/tsserverlibrary.d.ts | 11 ++++++++--- tests/baselines/reference/api/typescript.d.ts | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index f8c25f1369ff5..b156590ff9030 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -936,7 +936,7 @@ declare namespace ts { } export interface TypeOperatorNode extends TypeNode { readonly kind: SyntaxKind.TypeOperator; - readonly operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword; + readonly operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | SyntaxKind.ThrowKeyword; readonly type: TypeNode; } export interface IndexedAccessTypeNode extends TypeNode { @@ -978,7 +978,8 @@ declare namespace ts { Uppercase = 1, Lowercase = 2, Capitalize = 3, - Uncapitalize = 4 + Uncapitalize = 4, + TypeOf = 5 } export interface Expression extends Node { _expressionBrand: any; @@ -2477,6 +2478,7 @@ declare namespace ts { Substitution = 33554432, NonPrimitive = 67108864, TemplateLiteral = 134217728, + ThrowType = 268435456, Literal = 2944, Unit = 109440, StringOrNumberLiteral = 384, @@ -2525,6 +2527,9 @@ declare namespace ts { } export interface EnumType extends Type { } + export interface ThrowType extends Type { + value: Type; + } export enum ObjectFlags { Class = 1, Interface = 2, @@ -3246,7 +3251,7 @@ declare namespace ts { createParenthesizedType(type: TypeNode): ParenthesizedTypeNode; updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode): ParenthesizedTypeNode; createThisTypeNode(): ThisTypeNode; - createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode; + createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | SyntaxKind.ThrowKeyword, type: TypeNode): TypeOperatorNode; updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode): TypeOperatorNode; createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode; updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c21c6490c3588..b4bb7207b46f0 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -936,7 +936,7 @@ declare namespace ts { } export interface TypeOperatorNode extends TypeNode { readonly kind: SyntaxKind.TypeOperator; - readonly operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword; + readonly operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | SyntaxKind.ThrowKeyword; readonly type: TypeNode; } export interface IndexedAccessTypeNode extends TypeNode { @@ -978,7 +978,8 @@ declare namespace ts { Uppercase = 1, Lowercase = 2, Capitalize = 3, - Uncapitalize = 4 + Uncapitalize = 4, + TypeOf = 5 } export interface Expression extends Node { _expressionBrand: any; @@ -2477,6 +2478,7 @@ declare namespace ts { Substitution = 33554432, NonPrimitive = 67108864, TemplateLiteral = 134217728, + ThrowType = 268435456, Literal = 2944, Unit = 109440, StringOrNumberLiteral = 384, @@ -2525,6 +2527,9 @@ declare namespace ts { } export interface EnumType extends Type { } + export interface ThrowType extends Type { + value: Type; + } export enum ObjectFlags { Class = 1, Interface = 2, @@ -3246,7 +3251,7 @@ declare namespace ts { createParenthesizedType(type: TypeNode): ParenthesizedTypeNode; updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode): ParenthesizedTypeNode; createThisTypeNode(): ThisTypeNode; - createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode; + createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | SyntaxKind.ThrowKeyword, type: TypeNode): TypeOperatorNode; updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode): TypeOperatorNode; createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode; updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode;