Skip to content

Commit ff20f38

Browse files
committed
Add support for numbers and symbols in keyof (but keep it disabled)
1 parent ccf20d3 commit ff20f38

4 files changed

Lines changed: 73 additions & 14 deletions

File tree

src/compiler/checker.ts

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ namespace ts {
7272
const strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization");
7373
const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny");
7474
const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis");
75+
const keyofStringsOnly = true; // !!compilerOptions.keyofStringsOnly;
7576

7677
const emitResolver = createResolver();
7778
const nodeBuilder = createNodeBuilder();
@@ -346,6 +347,9 @@ namespace ts {
346347
const silentNeverType = createIntrinsicType(TypeFlags.Never, "never");
347348
const implicitNeverType = createIntrinsicType(TypeFlags.Never, "never");
348349
const nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object");
350+
const stringNumberType = getUnionType([stringType, numberType]);
351+
const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]);
352+
const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType;
349353

350354
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
351355

@@ -420,6 +424,7 @@ namespace ts {
420424
let deferredGlobalAsyncIteratorType: GenericType;
421425
let deferredGlobalAsyncIterableIteratorType: GenericType;
422426
let deferredGlobalTemplateStringsArrayType: ObjectType;
427+
let deferredGlobalExtractSymbol: Symbol;
423428

424429
let deferredNodes: Node[];
425430
let deferredUnusedIdentifierNodes: Node[];
@@ -4207,7 +4212,7 @@ namespace ts {
42074212
// right hand expression is of a type parameter type.
42084213
if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) {
42094214
const indexType = getIndexType(checkNonNullExpression(declaration.parent.parent.expression));
4210-
return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? indexType : stringType;
4215+
return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType;
42114216
}
42124217

42134218
if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) {
@@ -6015,6 +6020,7 @@ namespace ts {
60156020
function resolveMappedTypeMembers(type: MappedType) {
60166021
const members: SymbolTable = createSymbolTable();
60176022
let stringIndexInfo: IndexInfo;
6023+
let numberIndexInfo: IndexInfo;
60186024
// Resolve upfront such that recursive references see an empty object type.
60196025
setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined);
60206026
// In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type,
@@ -6025,15 +6031,19 @@ namespace ts {
60256031
const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T'
60266032
const templateModifiers = getMappedTypeModifiers(type);
60276033
const constraintDeclaration = type.declaration.typeParameter.constraint;
6034+
const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique;
60286035
if (constraintDeclaration.kind === SyntaxKind.TypeOperator &&
60296036
(<TypeOperatorNode>constraintDeclaration).operator === SyntaxKind.KeyOfKeyword) {
60306037
// We have a { [P in keyof T]: X }
60316038
for (const prop of getPropertiesOfType(modifiersType)) {
6032-
addMemberForKeyType(getLiteralTypeFromPropertyName(prop), undefined, prop);
6039+
addMemberForKeyType(getLiteralTypeFromPropertyName(prop, include), undefined, prop);
60336040
}
60346041
if (modifiersType.flags & TypeFlags.Any || getIndexInfoOfType(modifiersType, IndexKind.String)) {
60356042
addMemberForKeyType(stringType);
60366043
}
6044+
if (!keyofStringsOnly && getIndexInfoOfType(modifiersType, IndexKind.Number)) {
6045+
addMemberForKeyType(numberType);
6046+
}
60376047
}
60386048
else {
60396049
// First, if the constraint type is a type parameter, obtain the base constraint. Then,
@@ -6043,7 +6053,7 @@ namespace ts {
60436053
const iterationType = keyType.flags & TypeFlags.Index ? getIndexType(getApparentType((<IndexType>keyType).type)) : keyType;
60446054
forEachType(iterationType, addMemberForKeyType);
60456055
}
6046-
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
6056+
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
60476057

60486058
function addMemberForKeyType(t: Type, _index?: number, origin?: Symbol) {
60496059
// Create a mapper from T to the current iteration type constituent. Then, if the
@@ -6077,6 +6087,9 @@ namespace ts {
60776087
else if (t.flags & (TypeFlags.Any | TypeFlags.String)) {
60786088
stringIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly));
60796089
}
6090+
else if (t.flags & TypeFlags.Number) {
6091+
numberIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly));
6092+
}
60806093
}
60816094
}
60826095

@@ -6468,6 +6481,7 @@ namespace ts {
64686481
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
64696482
t.flags & TypeFlags.ESSymbolLike ? getGlobalESSymbolType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2015) :
64706483
t.flags & TypeFlags.NonPrimitive ? emptyObjectType :
6484+
t.flags & TypeFlags.Index ? keyofConstraintType :
64716485
t;
64726486
}
64736487

@@ -7665,6 +7679,10 @@ namespace ts {
76657679
return symbol && <GenericType>getTypeOfGlobalSymbol(symbol, arity);
76667680
}
76677681

7682+
function getGlobalExtractSymbol(): Symbol {
7683+
return deferredGlobalExtractSymbol || (deferredGlobalExtractSymbol = getGlobalSymbol("Extract" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0));
7684+
}
7685+
76687686
/**
76697687
* Instantiates a global type that is generic with some element type, and returns that instantiation.
76707688
*/
@@ -8107,11 +8125,11 @@ namespace ts {
81078125
return type.resolvedIndexType;
81088126
}
81098127

8110-
function getLiteralTypeFromPropertyName(prop: Symbol) {
8128+
function getLiteralTypeFromPropertyName(prop: Symbol, include: TypeFlags) {
81118129
if (!(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) {
81128130
const nameType = getLateBoundSymbol(prop).nameType;
81138131
if (nameType) {
8114-
return nameType.flags & TypeFlags.StringLiteral ? nameType : neverType;
8132+
return nameType.flags & include ? nameType : neverType;
81158133
}
81168134
if (!isKnownSymbol(prop)) {
81178135
return getLiteralType(symbolName(prop));
@@ -8120,21 +8138,45 @@ namespace ts {
81208138
return neverType;
81218139
}
81228140

8123-
function getLiteralTypeFromPropertyNames(type: Type) {
8124-
return getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName));
8141+
function getLiteralTypeFromPropertyNames(type: Type, include: TypeFlags) {
8142+
return getUnionType(map(getPropertiesOfType(type), t => getLiteralTypeFromPropertyName(t, include)));
8143+
}
8144+
8145+
function getNonEnumNumberIndexInfo(type: Type) {
8146+
const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number);
8147+
return numberIndexInfo !== enumNumberIndexInfo ? numberIndexInfo : undefined;
8148+
}
8149+
8150+
function getStringOnlyIndexType(type: Type) {
8151+
return type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? stringType :
8152+
getLiteralTypeFromPropertyNames(type, TypeFlags.StringLiteral);
8153+
}
8154+
8155+
function getStringNumberSymbolIndexType(type: Type) {
8156+
return type.flags & TypeFlags.Any ? stringNumberSymbolType :
8157+
getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringNumberType, getLiteralTypeFromPropertyNames(type, TypeFlags.UniqueESSymbol)]) :
8158+
getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromPropertyNames(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
8159+
getLiteralTypeFromPropertyNames(type, TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.UniqueESSymbol);
81258160
}
81268161

81278162
function getIndexType(type: Type): Type {
81288163
return type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t))) :
81298164
maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type) :
81308165
getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(<MappedType>type) :
81318166
type === wildcardType ? wildcardType :
8132-
type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? stringType :
8133-
getLiteralTypeFromPropertyNames(type);
8167+
keyofStringsOnly ? getStringOnlyIndexType(type) : getStringNumberSymbolIndexType(type);
8168+
}
8169+
8170+
function getExtractStringType(type: Type) {
8171+
if (keyofStringsOnly) {
8172+
return type;
8173+
}
8174+
const extractTypeAlias = getGlobalExtractSymbol();
8175+
return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType;
81348176
}
81358177

81368178
function getIndexTypeOrString(type: Type): Type {
8137-
const indexType = getIndexType(type);
8179+
const indexType = getExtractStringType(getIndexType(type));
81388180
return indexType.flags & TypeFlags.Never ? stringType : indexType;
81398181
}
81408182

@@ -8632,7 +8674,7 @@ namespace ts {
86328674
if (right.flags & TypeFlags.Union) {
86338675
return mapType(right, t => getSpreadType(left, t, symbol, typeFlags, objectFlags));
86348676
}
8635-
if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive)) {
8677+
if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) {
86368678
return left;
86378679
}
86388680

@@ -10379,6 +10421,12 @@ namespace ts {
1037910421
}
1038010422
}
1038110423
}
10424+
else if (source.flags & TypeFlags.Index) {
10425+
if (result = isRelatedTo(keyofConstraintType, target, reportErrors)) {
10426+
errorInfo = saveErrorInfo;
10427+
return result;
10428+
}
10429+
}
1038210430
else if (source.flags & TypeFlags.Conditional) {
1038310431
if (target.flags & TypeFlags.Conditional) {
1038410432
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
@@ -15190,7 +15238,7 @@ namespace ts {
1519015238
// type, and any union of these types (like string | number).
1519115239
if (links.resolvedType.flags & TypeFlags.Nullable ||
1519215240
!isTypeAssignableToKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) &&
15193-
!isTypeAssignableTo(links.resolvedType, getUnionType([stringType, numberType, esSymbolType]))) {
15241+
!isTypeAssignableTo(links.resolvedType, stringNumberSymbolType)) {
1519415242
error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any);
1519515243
}
1519615244
else {
@@ -20841,7 +20889,7 @@ namespace ts {
2084120889

2084220890
const type = <MappedType>getTypeFromMappedTypeNode(node);
2084320891
const constraintType = getConstraintTypeFromMappedType(type);
20844-
checkTypeAssignableTo(constraintType, stringType, node.typeParameter.constraint);
20892+
checkTypeAssignableTo(constraintType, keyofConstraintType, node.typeParameter.constraint);
2084520893
}
2084620894

2084720895
function checkTypeOperator(node: TypeOperatorNode) {

src/compiler/commandLineParser.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,12 @@ namespace ts {
678678
category: Diagnostics.Advanced_Options,
679679
description: Diagnostics.Disable_strict_checking_of_generic_signatures_in_function_types,
680680
},
681+
{
682+
name: "keyofStringsOnly",
683+
type: "boolean",
684+
category: Diagnostics.Advanced_Options,
685+
description: Diagnostics.Resolve_keyof_to_string_valued_property_names_only_no_numbers_or_symbols,
686+
},
681687
{
682688
// A list of plugins to load in the language service
683689
name: "plugins",

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3516,6 +3516,10 @@
35163516
"category": "Error",
35173517
"code": 6192
35183518
},
3519+
"Resolve 'keyof' to string valued property names only (no numbers or symbols).": {
3520+
"category": "Message",
3521+
"code": 6193
3522+
},
35193523
"Variable '{0}' implicitly has an '{1}' type.": {
35203524
"category": "Error",
35213525
"code": 7005

src/compiler/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3596,7 +3596,7 @@ namespace ts {
35963596
Intrinsic = Any | String | Number | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never | NonPrimitive,
35973597
/* @internal */
35983598
Primitive = String | Number | Boolean | Enum | EnumLiteral | ESSymbol | Void | Undefined | Null | Literal | UniqueESSymbol,
3599-
StringLike = String | StringLiteral | Index,
3599+
StringLike = String | StringLiteral,
36003600
NumberLike = Number | NumberLiteral | Enum,
36013601
BooleanLike = Boolean | BooleanLiteral,
36023602
EnumLike = Enum | EnumLiteral,
@@ -4144,6 +4144,7 @@ namespace ts {
41444144
inlineSources?: boolean;
41454145
isolatedModules?: boolean;
41464146
jsx?: JsxEmit;
4147+
keyofStringsOnly?: boolean;
41474148
lib?: string[];
41484149
/*@internal*/listEmittedFiles?: boolean;
41494150
/*@internal*/listFiles?: boolean;

0 commit comments

Comments
 (0)