diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e964824ca62..bc42d7be3a1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2219,6 +2219,11 @@ namespace ts { else if (type.flags & TypeFlags.StringOrNumberLiteral) { writer.writeStringLiteral(literalTypeToString(type)); } + else if (type.flags & TypeFlags.PropertyName) { + writer.writeKeyword("keyof"); + writeSpace(writer); + writeType((type).type, TypeFormatFlags.None); + } else { // Should never get here // { ... } @@ -5667,18 +5672,24 @@ namespace ts { 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]); + function getPropertyNameTypeForTypeParameter(type: TypeParameter) { + if (!type.resolvedPropertyNameType) { + type.resolvedPropertyNameType = createType(TypeFlags.PropertyName); + type.resolvedPropertyNameType.type = type; } - const propKeysType = getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName)); - return getIndexInfoOfType(type, IndexKind.Number) ? getUnionType([numberType, propKeysType]) : propKeysType; + return type.resolvedPropertyNameType; + } + + function getPropertyNameType(type: Type): Type { + return type.flags & TypeFlags.TypeParameter ? + getPropertyNameTypeForTypeParameter(type) : + getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName)); } function getTypeFromTypeOperatorNode(node: TypeOperatorNode) { const links = getNodeLinks(node); if (!links.resolvedType) { - links.resolvedType = getKeyOfType(getTypeFromTypeNodeNoAlias(node.type)); + links.resolvedType = getPropertyNameType(getTypeFromTypeNodeNoAlias(node.type)); } return links.resolvedType; } @@ -6105,6 +6116,9 @@ namespace ts { if (type.flags & TypeFlags.Intersection) { return getIntersectionType(instantiateList((type).types, mapper, instantiateType), type.aliasSymbol, mapper.targetTypes); } + if (type.flags & TypeFlags.PropertyName) { + return getPropertyNameType(instantiateType((type).type, mapper)); + } } return type; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b1299fc01fa..1d61e79c775 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2623,14 +2623,15 @@ namespace ts { Object = 1 << 15, // Object type Union = 1 << 16, // Union (T | U) Intersection = 1 << 17, // Intersection (T & U) + PropertyName = 1 << 18, // keyof T /* @internal */ - FreshLiteral = 1 << 18, // Fresh literal type + FreshLiteral = 1 << 19, // Fresh literal type /* @internal */ - ContainsWideningType = 1 << 19, // Type is or contains undefined or null widening type + ContainsWideningType = 1 << 20, // Type is or contains undefined or null widening type /* @internal */ - ContainsObjectLiteral = 1 << 20, // Type is or contains object literal type + ContainsObjectLiteral = 1 << 21, // Type is or contains object literal type /* @internal */ - ContainsAnyFunctionType = 1 << 21, // Type is or contains object literal type + ContainsAnyFunctionType = 1 << 22, // Type is or contains object literal type /* @internal */ Nullable = Undefined | Null, @@ -2643,7 +2644,7 @@ namespace ts { Intrinsic = Any | String | Number | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never, /* @internal */ Primitive = String | Number | Boolean | Enum | ESSymbol | Void | Undefined | Null | Literal, - StringLike = String | StringLiteral, + StringLike = String | StringLiteral | PropertyName, NumberLike = Number | NumberLiteral | Enum | EnumLiteral, BooleanLike = Boolean | BooleanLiteral, EnumLike = Enum | EnumLiteral, @@ -2816,9 +2817,15 @@ namespace ts { /* @internal */ resolvedApparentType: Type; /* @internal */ + resolvedPropertyNameType: PropertyNameType; + /* @internal */ isThisType?: boolean; } + export interface PropertyNameType extends Type { + type: TypeParameter; + } + export const enum SignatureKind { Call, Construct,