From caea4f3a50ece23a0d7a3adb20b2d6ee227e7e8a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 2 Aug 2017 11:54:29 -0700 Subject: [PATCH] Properly handle constraints for types like (T & { [x: string]: D })[K] --- src/compiler/checker.ts | 52 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a9a006c8ad8..0d3ac082f2a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5900,6 +5900,10 @@ 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; @@ -5971,11 +5975,18 @@ namespace ts { return stringType; } if (t.flags & TypeFlags.IndexedAccess) { + const transformed = getTransformedIndexedAccessType(t); + if (transformed) { + return getBaseConstraint(transformed); + } const baseObjectType = getBaseConstraint((t).objectType); const baseIndexType = getBaseConstraint((t).indexType); const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType) : undefined; return baseIndexedAccess && baseIndexedAccess !== unknownType ? getBaseConstraint(baseIndexedAccess) : undefined; } + if (isGenericMappedType(t)) { + return emptyObjectType; + } return t; } } @@ -7610,7 +7621,44 @@ namespace ts { false; } - function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) { + // Return true if the given type is a non-generic object type with a string index signature and no + // other members. + function isStringIndexOnlyType(type: Type) { + if (type.flags & TypeFlags.Object && !isGenericMappedType(type)) { + const t = resolveStructuredTypeMembers(type); + return t.properties.length === 0 && + t.callSignatures.length === 0 && t.constructSignatures.length === 0 && + t.stringIndexInfo && !t.numberIndexInfo; + } + 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. + function getTransformedIndexedAccessType(type: IndexedAccessType): Type { + const objectType = type.objectType; + if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((objectType).types, isStringIndexOnlyType)) { + const regularTypes: Type[] = []; + const stringIndexTypes: Type[] = []; + for (const t of (objectType).types) { + if (isStringIndexOnlyType(t)) { + stringIndexTypes.push(getIndexTypeOfType(t, IndexKind.String)); + } + else { + regularTypes.push(t); + } + } + return getUnionType([ + getIndexedAccessType(getIntersectionType(regularTypes), type.indexType), + getIntersectionType(stringIndexTypes) + ]); + } + 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. @@ -18662,6 +18710,8 @@ namespace ts { } function checkIndexedAccessType(node: IndexedAccessTypeNode) { + checkSourceElement(node.objectType); + checkSourceElement(node.indexType); checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); }