diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a256b632fac..714967d7f13 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3066,6 +3066,7 @@ namespace ts { case SyntaxKind.InterfaceDeclaration: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.ThisType: + case SyntaxKind.TypeOperator: case SyntaxKind.LiteralType: // Types and signatures are TypeScript syntax, and exclude all other facts. transformFlags = TransformFlags.AssertTypeScript; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5b85a932c05..e964824ca62 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5663,6 +5663,26 @@ namespace ts { return links.resolvedType; } + function getLiteralTypeFromPropertyName(prop: Symbol) { + return startsWith(prop.name, "__@") ? neverType : getLiteralTypeForText(TypeFlags.StringLiteral, unescapeIdentifier(prop.name)); + } + + function getKeyOfType(type: Type): Type { + if (getIndexInfoOfType(type, IndexKind.String)) { + return getUnionType([stringType, numberType]); + } + const propKeysType = getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName)); + return getIndexInfoOfType(type, IndexKind.Number) ? getUnionType([numberType, propKeysType]) : propKeysType; + } + + function getTypeFromTypeOperatorNode(node: TypeOperatorNode) { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getKeyOfType(getTypeFromTypeNodeNoAlias(node.type)); + } + return links.resolvedType; + } + function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: Node, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { const links = getNodeLinks(node); if (!links.resolvedType) { @@ -5822,6 +5842,8 @@ namespace ts { case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocFunctionType: return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node, aliasSymbol, aliasTypeArguments); + case SyntaxKind.TypeOperator: + return getTypeFromTypeOperatorNode(node); // This function assumes that an identifier or qualified name is a type expression // Callers should first ensure this by calling isTypeNode case SyntaxKind.Identifier: @@ -18225,7 +18247,8 @@ namespace ts { case SyntaxKind.IntersectionType: return checkUnionOrIntersectionType(node); case SyntaxKind.ParenthesizedType: - return checkSourceElement((node).type); + case SyntaxKind.TypeOperator: + return checkSourceElement((node).type); case SyntaxKind.FunctionDeclaration: return checkFunctionDeclaration(node); case SyntaxKind.Block: diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 6458183b23c..6fa1bf118aa 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -413,6 +413,8 @@ namespace ts { return emitIntersectionType(type); case SyntaxKind.ParenthesizedType: return emitParenType(type); + case SyntaxKind.TypeOperator: + return emitTypeOperator(type); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: return emitSignatureDeclarationWithJsDocComments(type); @@ -506,6 +508,12 @@ namespace ts { write(")"); } + function emitTypeOperator(type: TypeOperatorNode) { + write(tokenToString(type.operator)); + write(" "); + emitType(type.type); + } + function emitTypeLiteral(type: TypeLiteralNode) { write("{"); if (type.members.length) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 1051c9adca9..71b181b26af 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -594,6 +594,8 @@ const _super = (function (geti, seti) { return emitExpressionWithTypeArguments(node); case SyntaxKind.ThisType: return emitThisType(); + case SyntaxKind.TypeOperator: + return emitTypeOperator(node); case SyntaxKind.LiteralType: return emitLiteralType(node); @@ -1088,6 +1090,12 @@ const _super = (function (geti, seti) { write("this"); } + function emitTypeOperator(node: TypeOperatorNode) { + writeTokenText(node.operator); + write(" "); + emit(node.type); + } + function emitLiteralType(node: LiteralTypeNode) { emitExpression(node.literal); } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 8cdbf956d42..404cabfae55 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -134,7 +134,8 @@ namespace ts { case SyntaxKind.IntersectionType: return visitNodes(cbNodes, (node).types); case SyntaxKind.ParenthesizedType: - return visitNode(cbNode, (node).type); + case SyntaxKind.TypeOperator: + return visitNode(cbNode, (node).type); case SyntaxKind.LiteralType: return visitNode(cbNode, (node).literal); case SyntaxKind.ObjectBindingPattern: @@ -2526,6 +2527,22 @@ namespace ts { return type; } + function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword) { + const node = createNode(SyntaxKind.TypeOperator); + parseExpected(operator); + node.operator = operator; + node.type = parseTypeOperatorOrHigher(); + return finishNode(node); + } + + function parseTypeOperatorOrHigher(): TypeNode { + switch (token()) { + case SyntaxKind.KeyOfKeyword: + return parseTypeOperator(SyntaxKind.KeyOfKeyword); + } + return parseArrayTypeOrHigher(); + } + function parseUnionOrIntersectionType(kind: SyntaxKind, parseConstituentType: () => TypeNode, operator: SyntaxKind): TypeNode { let type = parseConstituentType(); if (token() === operator) { @@ -2542,7 +2559,7 @@ namespace ts { } function parseIntersectionTypeOrHigher(): TypeNode { - return parseUnionOrIntersectionType(SyntaxKind.IntersectionType, parseArrayTypeOrHigher, SyntaxKind.AmpersandToken); + return parseUnionOrIntersectionType(SyntaxKind.IntersectionType, parseTypeOperatorOrHigher, SyntaxKind.AmpersandToken); } function parseUnionTypeOrHigher(): TypeNode { diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index ab76c7ffa19..d0b051049de 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -90,6 +90,7 @@ namespace ts { "instanceof": SyntaxKind.InstanceOfKeyword, "interface": SyntaxKind.InterfaceKeyword, "is": SyntaxKind.IsKeyword, + "keyof": SyntaxKind.KeyOfKeyword, "let": SyntaxKind.LetKeyword, "module": SyntaxKind.ModuleKeyword, "namespace": SyntaxKind.NamespaceKeyword, diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 925869da385..4c8e79703e7 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -288,6 +288,7 @@ namespace ts { case SyntaxKind.IntersectionType: case SyntaxKind.ParenthesizedType: case SyntaxKind.ThisType: + case SyntaxKind.TypeOperator: case SyntaxKind.LiteralType: // TypeScript type nodes are elided. @@ -1856,6 +1857,7 @@ namespace ts { } // Fallthrough case SyntaxKind.TypeQuery: + case SyntaxKind.TypeOperator: case SyntaxKind.TypeLiteral: case SyntaxKind.AnyKeyword: case SyntaxKind.ThisType: diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f9ec3e9f268..b1299fc01fa 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -168,6 +168,7 @@ namespace ts { DeclareKeyword, GetKeyword, IsKeyword, + KeyOfKeyword, ModuleKeyword, NamespaceKeyword, NeverKeyword, @@ -216,6 +217,7 @@ namespace ts { IntersectionType, ParenthesizedType, ThisType, + TypeOperator, LiteralType, // Binding patterns ObjectBindingPattern, @@ -870,6 +872,12 @@ namespace ts { type: TypeNode; } + export interface TypeOperatorNode extends TypeNode { + kind: SyntaxKind.TypeOperator; + operator: SyntaxKind.KeyOfKeyword; + type: TypeNode; + } + export interface LiteralTypeNode extends TypeNode { kind: SyntaxKind.LiteralType; literal: Expression;