@@ -120,6 +120,7 @@ namespace ts {
120120 const intersectionTypes = createMap<IntersectionType>();
121121 const stringLiteralTypes = createMap<LiteralType>();
122122 const numericLiteralTypes = createMap<LiteralType>();
123+ const indexedAccessTypes = createMap<IndexedAccessType>();
123124 const evolvingArrayTypes: EvolvingArrayType[] = [];
124125
125126 const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown");
@@ -4665,13 +4666,22 @@ namespace ts {
46654666 return type.resolvedApparentType;
46664667 }
46674668
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+
46684676 /**
46694677 * For a type parameter, return the base constraint of the type parameter. For the string, number,
46704678 * boolean, and symbol primitive types, return the corresponding object types. Otherwise return the
46714679 * type itself. Note that the apparent type of a union type is the union type itself.
46724680 */
46734681 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;
46754685 return t.flags & TypeFlags.StringLike ? globalStringType :
46764686 t.flags & TypeFlags.NumberLike ? globalNumberType :
46774687 t.flags & TypeFlags.BooleanLike ? globalBooleanType :
@@ -5907,6 +5917,7 @@ namespace ts {
59075917
59085918 function getIndexType(type: Type): Type {
59095919 return type.flags & TypeFlags.TypeParameter ? getIndexTypeForTypeParameter(<TypeParameter>type) :
5920+ getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(<MappedType>type) :
59105921 type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? stringOrNumberType :
59115922 getIndexInfoOfType(type, IndexKind.Number) ? getUnionType([numberType, getLiteralTypeFromPropertyNames(type)]) :
59125923 getLiteralTypeFromPropertyNames(type);
@@ -5920,18 +5931,13 @@ namespace ts {
59205931 return links.resolvedType;
59215932 }
59225933
5923- function createIndexedAccessType(objectType: Type, indexType: TypeParameter ) {
5934+ function createIndexedAccessType(objectType: Type, indexType: Type ) {
59245935 const type = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
59255936 type.objectType = objectType;
59265937 type.indexType = indexType;
59275938 return type;
59285939 }
59295940
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-
59355941 function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode, cacheSymbol: boolean) {
59365942 const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? <ElementAccessExpression>accessNode : undefined;
59375943 const propName = indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.EnumLiteral) ?
@@ -5995,13 +6001,41 @@ namespace ts {
59956001 return unknownType;
59966002 }
59976003
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+
59986015 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);
60036035 }
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));
60056039 }
60066040 const apparentType = getApparentType(objectType);
60076041 if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Primitive)) {
@@ -6034,6 +6068,9 @@ namespace ts {
60346068 type.aliasSymbol = getAliasSymbolForTypeNode(node);
60356069 type.aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node);
60366070 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);
60376074 }
60386075 return links.resolvedType;
60396076 }
@@ -7153,12 +7190,24 @@ namespace ts {
71537190 }
71547191
71557192 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+ }
71627211 }
71637212 }
71647213 }
@@ -7178,22 +7227,41 @@ namespace ts {
71787227 }
71797228 }
71807229 }
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+ }
71817239
71827240 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+ }
71877248 }
7249+ else {
7250+ let constraint = getConstraintOfTypeParameter(<TypeParameter>source);
71887251
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+ }
71917255
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+ }
71977265 }
71987266 }
71997267 else {
0 commit comments