diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cc4bac59b19f3..71b563720a8c9 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); @@ -4493,6 +4504,9 @@ namespace ts { if (type.flags & TypeFlags.Substitution) { return typeToTypeNodeHelper((type).baseType, context); } + if (type.flags & TypeFlags.ThrowType) { + return typeToTypeNodeHelper(neverType, context); + } return Debug.fail("Should be unreachable."); @@ -13391,6 +13405,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); } @@ -13422,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); @@ -13449,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) : @@ -14461,6 +14484,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 +15142,10 @@ namespace ts { return sub; } } + if (flags & TypeFlags.ThrowType) { + errorByThrowType(currentNode, instantiateType((type).value, mapper)); + return errorType; + } return type; } @@ -19312,6 +19345,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); } @@ -27774,6 +27810,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)) { @@ -31391,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/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/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/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..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; } @@ -3585,7 +3586,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 +3616,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..796545024e851 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; } @@ -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. @@ -4873,6 +4874,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 +5010,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 +6787,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; 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;