Skip to content

Commit 4b50ef3

Browse files
committed
Consider index signatures in type produced by 'keyof T'
1 parent e7cfbfe commit 4b50ef3

File tree

3 files changed

+62
-49
lines changed

3 files changed

+62
-49
lines changed

src/compiler/checker.ts

Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ namespace ts {
136136
const voidType = createIntrinsicType(TypeFlags.Void, "void");
137137
const neverType = createIntrinsicType(TypeFlags.Never, "never");
138138
const silentNeverType = createIntrinsicType(TypeFlags.Never, "never");
139+
const stringOrNumberType = getUnionType([stringType, numberType]);
139140

140141
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
141142
const emptyGenericType = <GenericType><ObjectType>createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
@@ -4473,22 +4474,13 @@ namespace ts {
44734474
* type itself. Note that the apparent type of a union type is the union type itself.
44744475
*/
44754476
function getApparentType(type: Type): Type {
4476-
if (type.flags & TypeFlags.TypeParameter) {
4477-
type = getApparentTypeOfTypeParameter(<TypeParameter>type);
4478-
}
4479-
if (type.flags & TypeFlags.StringLike) {
4480-
type = globalStringType;
4481-
}
4482-
else if (type.flags & TypeFlags.NumberLike) {
4483-
type = globalNumberType;
4484-
}
4485-
else if (type.flags & TypeFlags.BooleanLike) {
4486-
type = globalBooleanType;
4487-
}
4488-
else if (type.flags & TypeFlags.ESSymbol) {
4489-
type = getGlobalESSymbolType();
4490-
}
4491-
return type;
4477+
const t = type.flags & TypeFlags.TypeParameter ? getApparentTypeOfTypeParameter(<TypeParameter>type) : type;
4478+
return t.flags & TypeFlags.StringLike ? globalStringType :
4479+
t.flags & TypeFlags.NumberLike ? globalNumberType :
4480+
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
4481+
t.flags & TypeFlags.ESSymbol ? getGlobalESSymbolType() :
4482+
t.flags & TypeFlags.Index ? stringOrNumberType :
4483+
t;
44924484
}
44934485

44944486
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: string): Symbol {
@@ -5674,10 +5666,6 @@ namespace ts {
56745666
return links.resolvedType;
56755667
}
56765668

5677-
function getLiteralTypeFromPropertyName(prop: Symbol) {
5678-
return startsWith(prop.name, "__@") ? neverType : getLiteralTypeForText(TypeFlags.StringLiteral, unescapeIdentifier(prop.name));
5679-
}
5680-
56815669
function getIndexTypeForTypeParameter(type: TypeParameter) {
56825670
if (!type.resolvedIndexType) {
56835671
type.resolvedIndexType = <IndexType>createType(TypeFlags.Index);
@@ -5686,10 +5674,19 @@ namespace ts {
56865674
return type.resolvedIndexType;
56875675
}
56885676

5677+
function getLiteralTypeFromPropertyName(prop: Symbol) {
5678+
return startsWith(prop.name, "__@") ? neverType : getLiteralTypeForText(TypeFlags.StringLiteral, unescapeIdentifier(prop.name));
5679+
}
5680+
5681+
function getLiteralTypeFromPropertyNames(type: Type) {
5682+
return getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName));
5683+
}
5684+
56895685
function getIndexType(type: Type): Type {
5690-
return type.flags & TypeFlags.TypeParameter ?
5691-
getIndexTypeForTypeParameter(<TypeParameter>type) :
5692-
getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName));
5686+
return type.flags & TypeFlags.TypeParameter ? getIndexTypeForTypeParameter(<TypeParameter>type) :
5687+
getIndexInfoOfType(type, IndexKind.String) ? stringOrNumberType :
5688+
getIndexInfoOfType(type, IndexKind.Number) ? getUnionType([numberType, getLiteralTypeFromPropertyNames(type)]) :
5689+
getLiteralTypeFromPropertyNames(type);
56935690
}
56945691

56955692
function getTypeFromTypeOperatorNode(node: TypeOperatorNode) {
@@ -5712,33 +5709,45 @@ namespace ts {
57125709
return indexedAccessTypes[objectType.id] || (indexedAccessTypes[objectType.id] = createIndexedAccessType(objectType, keyType));
57135710
}
57145711

5712+
function getPropertyTypeForIndexType(objectType: Type, indexType: Type) {
5713+
return indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.EnumLiteral) && getTypeOfPropertyOfType(objectType, escapeIdentifier((<LiteralType>indexType).text)) ||
5714+
isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike) && getIndexTypeOfType(objectType, IndexKind.Number) ||
5715+
isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike) && getIndexTypeOfType(objectType, IndexKind.String) ||
5716+
undefined;
5717+
}
5718+
57155719
function getIndexedAccessType(objectType: Type, keyType: Type) {
5716-
if (keyType.flags & TypeFlags.TypeParameter) {
5717-
return getIndexedAccessTypeForTypeParameter(objectType, <TypeParameter>keyType);
5718-
}
5719-
if (isTypeOfKind(keyType, TypeFlags.StringLiteral) && !(keyType.flags & TypeFlags.Intersection)) {
5720-
return mapType(keyType, t => getTypeOfPropertyOfType(objectType, escapeIdentifier((<LiteralType>t).text)) || unknownType);
5721-
}
5722-
return keyType.flags & TypeFlags.Any ? anyType : unknownType;
5720+
return keyType.flags & TypeFlags.Any ? anyType :
5721+
keyType.flags & TypeFlags.TypeParameter ? getIndexedAccessTypeForTypeParameter(objectType, <TypeParameter>keyType) :
5722+
mapType(keyType, t => getPropertyTypeForIndexType(objectType, t) || unknownType);
57235723
}
57245724

57255725
function resolveIndexedAccessTypeNode(node: IndexedAccessTypeNode) {
57265726
const objectType = getTypeFromTypeNodeNoAlias(node.objectType);
5727-
const keyType = getTypeFromTypeNodeNoAlias(node.indexType);
5728-
if (keyType.flags & TypeFlags.TypeParameter &&
5729-
getConstraintOfTypeParameter(<TypeParameter>keyType) === getIndexType(objectType)) {
5730-
return getIndexedAccessType(objectType, keyType);
5731-
}
5732-
if (isTypeOfKind(keyType, TypeFlags.StringLiteral) && !(keyType.flags & TypeFlags.Intersection)) {
5733-
const missing = forEachType(keyType, t => getTypeOfPropertyOfType(objectType, escapeIdentifier((<LiteralType>t).text)) ? undefined : (<LiteralType>t).text);
5734-
if (missing) {
5735-
error(node.indexType, Diagnostics.Property_0_is_missing_in_type_1, missing, typeToString(objectType));
5727+
const indexType = getTypeFromTypeNodeNoAlias(node.indexType);
5728+
if (indexType.flags & TypeFlags.TypeParameter) {
5729+
if (!isTypeAssignableTo(getConstraintOfTypeParameter(<TypeParameter>indexType), getIndexType(objectType))) {
5730+
error(node.indexType, Diagnostics.Type_0_is_not_constrained_to_keyof_1, typeToString(indexType), typeToString(objectType));
57365731
return unknownType;
57375732
}
5738-
return getIndexedAccessType(objectType, keyType);
5733+
return getIndexedAccessType(objectType, indexType);
57395734
}
5740-
error(node.indexType, Diagnostics.Property_access_element_type_must_be_a_string_literal_type_or_a_type_parameter_constrained_to_keyof_0, typeToString(objectType));
5741-
return unknownType;
5735+
const indexTypes = indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Primitive) ? (<UnionType>indexType).types : [indexType];
5736+
for (const t of indexTypes) {
5737+
if (!getPropertyTypeForIndexType(objectType, t)) {
5738+
if (t.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
5739+
error(node.indexType, Diagnostics.Property_0_does_not_exist_on_type_1, (<LiteralType>t).text, typeToString(objectType))
5740+
}
5741+
else if (t.flags & (TypeFlags.String | TypeFlags.Number)) {
5742+
error(node.indexType, Diagnostics.Type_0_has_no_matching_index_signature_for_type_1, typeToString(objectType), typeToString(t));
5743+
}
5744+
else {
5745+
error(node.indexType, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(t));
5746+
}
5747+
return unknownType;
5748+
}
5749+
}
5750+
return getIndexedAccessType(objectType, indexType);
57425751
}
57435752

57445753
function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) {
@@ -8606,10 +8615,6 @@ namespace ts {
86068615
return containsType(target.types, source);
86078616
}
86088617

8609-
function forEachType<T>(type: Type, f: (t: Type) => T): T {
8610-
return type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, f) : f(type);
8611-
}
8612-
86138618
function filterType(type: Type, f: (t: Type) => boolean): Type {
86148619
if (type.flags & TypeFlags.Union) {
86158620
const types = (<UnionType>type).types;

src/compiler/diagnosticMessages.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1751,10 +1751,18 @@
17511751
"category": "Error",
17521752
"code": 2535
17531753
},
1754-
"Property access element type must be a string literal type or a type parameter constrained to 'keyof {0}'.": {
1754+
"Type '{0}' is not constrained to 'keyof {1}'.": {
17551755
"category": "Error",
17561756
"code": 2536
17571757
},
1758+
"Type '{0}' has no matching index signature for type '{1}'.": {
1759+
"category": "Error",
1760+
"code": 2537
1761+
},
1762+
"Type '{0}' cannot be used as an index type.": {
1763+
"category": "Error",
1764+
"code": 2538
1765+
},
17581766
"JSX element attributes type '{0}' may not be a union type.": {
17591767
"category": "Error",
17601768
"code": 2600

src/compiler/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2652,7 +2652,7 @@ namespace ts {
26522652
Intrinsic = Any | String | Number | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never,
26532653
/* @internal */
26542654
Primitive = String | Number | Boolean | Enum | ESSymbol | Void | Undefined | Null | Literal,
2655-
StringLike = String | StringLiteral | Index,
2655+
StringLike = String | StringLiteral,
26562656
NumberLike = Number | NumberLiteral | Enum | EnumLiteral,
26572657
BooleanLike = Boolean | BooleanLiteral,
26582658
EnumLike = Enum | EnumLiteral,
@@ -2662,7 +2662,7 @@ namespace ts {
26622662

26632663
// 'Narrowable' types are types where narrowing actually narrows.
26642664
// This *should* be every type other than null, undefined, void, and never
2665-
Narrowable = Any | StructuredType | TypeParameter | IndexedAccess | StringLike | NumberLike | BooleanLike | ESSymbol,
2665+
Narrowable = Any | StructuredType | TypeParameter | Index | IndexedAccess | StringLike | NumberLike | BooleanLike | ESSymbol,
26662666
NotUnionOrUnit = Any | ESSymbol | Object,
26672667
/* @internal */
26682668
RequiresWidening = ContainsWideningType | ContainsObjectLiteral,

0 commit comments

Comments
 (0)