@@ -120,6 +120,7 @@ namespace ts {
120
120
const intersectionTypes = createMap<IntersectionType>();
121
121
const stringLiteralTypes = createMap<LiteralType>();
122
122
const numericLiteralTypes = createMap<LiteralType>();
123
+ const indexedAccessTypes = createMap<IndexedAccessType>();
123
124
const evolvingArrayTypes: EvolvingArrayType[] = [];
124
125
125
126
const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown");
@@ -4665,13 +4666,22 @@ namespace ts {
4665
4666
return type.resolvedApparentType;
4666
4667
}
4667
4668
4669
+ /**
4670
+ * The apparent type of an indexed access T[K] is the type of T's string index signature, if any.
4671
+ */
4672
+ function getApparentTypeOfIndexedAccess(type: IndexedAccessType) {
4673
+ return getIndexTypeOfType(getApparentType(type.objectType), IndexKind.String) || type;
4674
+ }
4675
+
4668
4676
/**
4669
4677
* For a type parameter, return the base constraint of the type parameter. For the string, number,
4670
4678
* boolean, and symbol primitive types, return the corresponding object types. Otherwise return the
4671
4679
* type itself. Note that the apparent type of a union type is the union type itself.
4672
4680
*/
4673
4681
function getApparentType(type: Type): Type {
4674
- const t = type.flags & TypeFlags.TypeParameter ? getApparentTypeOfTypeParameter(<TypeParameter>type) : type;
4682
+ const t = type.flags & TypeFlags.TypeParameter ? getApparentTypeOfTypeParameter(<TypeParameter>type) :
4683
+ type.flags & TypeFlags.IndexedAccess ? getApparentTypeOfIndexedAccess(<IndexedAccessType>type) :
4684
+ type;
4675
4685
return t.flags & TypeFlags.StringLike ? globalStringType :
4676
4686
t.flags & TypeFlags.NumberLike ? globalNumberType :
4677
4687
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
@@ -5907,6 +5917,7 @@ namespace ts {
5907
5917
5908
5918
function getIndexType(type: Type): Type {
5909
5919
return type.flags & TypeFlags.TypeParameter ? getIndexTypeForTypeParameter(<TypeParameter>type) :
5920
+ getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(<MappedType>type) :
5910
5921
type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? stringOrNumberType :
5911
5922
getIndexInfoOfType(type, IndexKind.Number) ? getUnionType([numberType, getLiteralTypeFromPropertyNames(type)]) :
5912
5923
getLiteralTypeFromPropertyNames(type);
@@ -5920,18 +5931,13 @@ namespace ts {
5920
5931
return links.resolvedType;
5921
5932
}
5922
5933
5923
- function createIndexedAccessType(objectType: Type, indexType: TypeParameter ) {
5934
+ function createIndexedAccessType(objectType: Type, indexType: Type ) {
5924
5935
const type = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
5925
5936
type.objectType = objectType;
5926
5937
type.indexType = indexType;
5927
5938
return type;
5928
5939
}
5929
5940
5930
- function getIndexedAccessTypeForTypeParameter(objectType: Type, indexType: TypeParameter) {
5931
- const indexedAccessTypes = indexType.resolvedIndexedAccessTypes || (indexType.resolvedIndexedAccessTypes = []);
5932
- return indexedAccessTypes[objectType.id] || (indexedAccessTypes[objectType.id] = createIndexedAccessType(objectType, indexType));
5933
- }
5934
-
5935
5941
function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode, cacheSymbol: boolean) {
5936
5942
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? <ElementAccessExpression>accessNode : undefined;
5937
5943
const propName = indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.EnumLiteral) ?
@@ -5995,13 +6001,41 @@ namespace ts {
5995
6001
return unknownType;
5996
6002
}
5997
6003
6004
+ function getIndexedAccessForMappedType(type: MappedType, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) {
6005
+ const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? <ElementAccessExpression>accessNode : undefined;
6006
+ if (accessExpression && isAssignmentTarget(accessExpression) && type.declaration.readonlyToken) {
6007
+ error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(type));
6008
+ return unknownType;
6009
+ }
6010
+ const mapper = createUnaryTypeMapper(getTypeParameterFromMappedType(type), indexType);
6011
+ const templateMapper = type.mapper ? combineTypeMappers(type.mapper, mapper) : mapper;
6012
+ return addOptionality(instantiateType(getTemplateTypeFromMappedType(type), templateMapper), !!type.declaration.questionToken);
6013
+ }
6014
+
5998
6015
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) {
5999
- if (indexType.flags & TypeFlags.TypeParameter) {
6000
- if (accessNode && !isTypeAssignableTo(getConstraintOfTypeParameter(<TypeParameter>indexType) || emptyObjectType, getIndexType(objectType))) {
6001
- error(accessNode, Diagnostics.Type_0_is_not_constrained_to_keyof_1, typeToString(indexType), typeToString(objectType));
6002
- return unknownType;
6016
+ if (indexType.flags & TypeFlags.TypeParameter ||
6017
+ objectType.flags & TypeFlags.TypeParameter && indexType.flags & TypeFlags.Index ||
6018
+ isGenericMappedType(objectType)) {
6019
+ // If either the object type or the index type are type parameters, or if the object type is a mapped
6020
+ // type with a generic constraint, we are performing a higher-order index access where we cannot
6021
+ // meaningfully access the properties of the object type. In those cases, we first check that the
6022
+ // index type is assignable to 'keyof T' for the object type.
6023
+ if (accessNode) {
6024
+ const keyType = indexType.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(<TypeParameter>indexType) || emptyObjectType : indexType;
6025
+ if (!isTypeAssignableTo(keyType, getIndexType(objectType))) {
6026
+ error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType));
6027
+ return unknownType;
6028
+ }
6029
+ }
6030
+ // If the object type is a mapped type { [P in K]: E }, we instantiate E using a mapper that substitutes
6031
+ // the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we construct the
6032
+ // type Box<T[X]>.
6033
+ if (isGenericMappedType(objectType)) {
6034
+ return getIndexedAccessForMappedType(<MappedType>objectType, indexType, accessNode);
6003
6035
}
6004
- return getIndexedAccessTypeForTypeParameter(objectType, <TypeParameter>indexType);
6036
+ // Otherwise we defer the operation by creating an indexed access type.
6037
+ const id = objectType.id + "," + indexType.id;
6038
+ return indexedAccessTypes[id] || (indexedAccessTypes[id] = createIndexedAccessType(objectType, indexType));
6005
6039
}
6006
6040
const apparentType = getApparentType(objectType);
6007
6041
if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Primitive)) {
@@ -6034,6 +6068,9 @@ namespace ts {
6034
6068
type.aliasSymbol = getAliasSymbolForTypeNode(node);
6035
6069
type.aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node);
6036
6070
links.resolvedType = type;
6071
+ // Eagerly resolve the constraint type which forces an error if the constraint type circularly
6072
+ // references itself through one or more type aliases.
6073
+ getConstraintTypeFromMappedType(type);
6037
6074
}
6038
6075
return links.resolvedType;
6039
6076
}
@@ -7153,12 +7190,24 @@ namespace ts {
7153
7190
}
7154
7191
7155
7192
if (target.flags & TypeFlags.TypeParameter) {
7156
- // Given a type parameter K with a constraint keyof T, a type S is
7157
- // assignable to K if S is assignable to keyof T.
7158
- const constraint = getConstraintOfTypeParameter(<TypeParameter>target);
7159
- if (constraint && constraint.flags & TypeFlags.Index) {
7160
- if (result = isRelatedTo(source, constraint, reportErrors)) {
7161
- return result;
7193
+ // A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P].
7194
+ if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(<MappedType>source) === getIndexType(target)) {
7195
+ if (!(<MappedType>source).declaration.questionToken) {
7196
+ const templateType = getTemplateTypeFromMappedType(<MappedType>source);
7197
+ const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(<MappedType>source));
7198
+ if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) {
7199
+ return result;
7200
+ }
7201
+ }
7202
+ }
7203
+ else {
7204
+ // Given a type parameter K with a constraint keyof T, a type S is
7205
+ // assignable to K if S is assignable to keyof T.
7206
+ const constraint = getConstraintOfTypeParameter(<TypeParameter>target);
7207
+ if (constraint && constraint.flags & TypeFlags.Index) {
7208
+ if (result = isRelatedTo(source, constraint, reportErrors)) {
7209
+ return result;
7210
+ }
7162
7211
}
7163
7212
}
7164
7213
}
@@ -7178,22 +7227,41 @@ namespace ts {
7178
7227
}
7179
7228
}
7180
7229
}
7230
+ else if (target.flags & TypeFlags.IndexedAccess) {
7231
+ // if we have indexed access types with identical index types, see if relationship holds for
7232
+ // the two object types.
7233
+ if (source.flags & TypeFlags.IndexedAccess && (<IndexedAccessType>source).indexType === (<IndexedAccessType>target).indexType) {
7234
+ if (result = isRelatedTo((<IndexedAccessType>source).objectType, (<IndexedAccessType>target).objectType, reportErrors)) {
7235
+ return result;
7236
+ }
7237
+ }
7238
+ }
7181
7239
7182
7240
if (source.flags & TypeFlags.TypeParameter) {
7183
- let constraint = getConstraintOfTypeParameter(<TypeParameter>source);
7184
-
7185
- if (!constraint || constraint.flags & TypeFlags.Any) {
7186
- constraint = emptyObjectType;
7241
+ // A source type T is related to a target type { [P in keyof T]: X } if T[P] is related to X.
7242
+ if (getObjectFlags(target) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(<MappedType>target) === getIndexType(source)) {
7243
+ const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(<MappedType>target));
7244
+ const templateType = getTemplateTypeFromMappedType(<MappedType>target);
7245
+ if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
7246
+ return result;
7247
+ }
7187
7248
}
7249
+ else {
7250
+ let constraint = getConstraintOfTypeParameter(<TypeParameter>source);
7188
7251
7189
- // The constraint may need to be further instantiated with its 'this' type.
7190
- constraint = getTypeWithThisArgument(constraint, source);
7252
+ if (!constraint || constraint.flags & TypeFlags.Any) {
7253
+ constraint = emptyObjectType;
7254
+ }
7191
7255
7192
- // Report constraint errors only if the constraint is not the empty object type
7193
- const reportConstraintErrors = reportErrors && constraint !== emptyObjectType;
7194
- if (result = isRelatedTo(constraint, target, reportConstraintErrors)) {
7195
- errorInfo = saveErrorInfo;
7196
- return result;
7256
+ // The constraint may need to be further instantiated with its 'this' type.
7257
+ constraint = getTypeWithThisArgument(constraint, source);
7258
+
7259
+ // Report constraint errors only if the constraint is not the empty object type
7260
+ const reportConstraintErrors = reportErrors && constraint !== emptyObjectType;
7261
+ if (result = isRelatedTo(constraint, target, reportConstraintErrors)) {
7262
+ errorInfo = saveErrorInfo;
7263
+ return result;
7264
+ }
7197
7265
}
7198
7266
}
7199
7267
else {
0 commit comments