From 616bb5fcf61bd9059147224b3aff3ec153fa0bd3 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 25 Aug 2017 07:10:53 -0700 Subject: [PATCH] Defer mapped type indexed access transformations --- src/compiler/checker.ts | 59 +++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4a07f349c9..338af6b3ba 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5906,10 +5906,6 @@ namespace ts { } function getConstraintOfIndexedAccess(type: IndexedAccessType) { - const transformed = getTransformedIndexedAccessType(type); - if (transformed) { - return transformed; - } const baseObjectType = getBaseConstraintOfType(type.objectType); const baseIndexType = getBaseConstraintOfType(type.indexType); return baseObjectType || baseIndexType ? getIndexedAccessType(baseObjectType || type.objectType, baseIndexType || type.indexType) : undefined; @@ -7596,22 +7592,6 @@ namespace ts { return anyType; } - function getIndexedAccessForMappedType(type: MappedType, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) { - if (accessNode) { - // Check if the index type is assignable to 'keyof T' for the object type. - if (!isTypeAssignableTo(indexType, getIndexType(type))) { - error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(type)); - return unknownType; - } - if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && type.declaration.readonlyToken) { - error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(type)); - } - } - const mapper = createTypeMapper([getTypeParameterFromMappedType(type)], [indexType]); - const templateMapper = type.mapper ? combineTypeMappers(type.mapper, mapper) : mapper; - return instantiateType(getTemplateTypeFromMappedType(type), templateMapper); - } - function isGenericObjectType(type: Type): boolean { return type.flags & TypeFlags.TypeVariable ? true : getObjectFlags(type) & ObjectFlags.Mapped ? isGenericIndexType(getConstraintTypeFromMappedType(type)) : @@ -7637,12 +7617,14 @@ namespace ts { return false; } - // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or - // more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a - // transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed - // access types with default property values as expressed by D. + // Transform an indexed access occurring in a read position to a simpler form. Return the simpler form, + // or undefined if no transformation is possible. function getTransformedIndexedAccessType(type: IndexedAccessType): Type { const objectType = type.objectType; + // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or + // more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a + // transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed + // access types with default property values as expressed by D. if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((objectType).types, isStringIndexOnlyType)) { const regularTypes: Type[] = []; const stringIndexTypes: Type[] = []; @@ -7659,20 +7641,23 @@ namespace ts { getIntersectionType(stringIndexTypes) ]); } + // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper + // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we + // construct the type Box. + if (isGenericMappedType(objectType)) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [type.indexType]); + const objectTypeMapper = (objectType).mapper; + const templateMapper = objectTypeMapper ? combineTypeMappers(objectTypeMapper, mapper) : mapper; + return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); + } return undefined; } function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode): Type { - // If the object type is a mapped type { [P in K]: E }, where K is generic, we instantiate E using a mapper - // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we - // construct the type Box. - if (isGenericMappedType(objectType)) { - return getIndexedAccessForMappedType(objectType, indexType, accessNode); - } - // Otherwise, if the index type is generic, or if the object type is generic and doesn't originate in an - // expression, we are performing a higher-order index access where we cannot meaningfully access the properties - // of the object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates - // in an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]' + // If the index type is generic, or if the object type is generic and doesn't originate in an expression, + // we are performing a higher-order index access where we cannot meaningfully access the properties of the + // object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in + // an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]' // has always been resolved eagerly using the constraint type of 'this' at the given location. if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) && isGenericObjectType(objectType)) { if (objectType.flags & TypeFlags.Any) { @@ -9334,7 +9319,7 @@ namespace ts { else if (source.flags & TypeFlags.IndexedAccess) { // A type S[K] is related to a type T if A[K] is related to T, where K is string-like and // A is the apparent type of S. - const constraint = getConstraintOfType(source); + const constraint = getTransformedIndexedAccessType(source) || getConstraintOfIndexedAccess(source); if (constraint) { if (result = isRelatedTo(constraint, target, reportErrors)) { errorInfo = saveErrorInfo; @@ -18810,6 +18795,10 @@ namespace ts { const objectType = (type).objectType; const indexType = (type).indexType; if (isTypeAssignableTo(indexType, getIndexType(objectType))) { + if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && + getObjectFlags(objectType) & ObjectFlags.Mapped && (objectType).declaration.readonlyToken) { + error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); + } return type; } // Check if we're indexing with a numeric type and if either object or index types