From 5895057578b47230ddb1a7195859b760737f9351 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 30 Jul 2017 17:44:38 -0700 Subject: [PATCH] Defer indexed access type resolution in more cases --- src/compiler/checker.ts | 59 ++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4de0c7a2a6..a9a006c8ad 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2583,10 +2583,8 @@ namespace ts { } function createTypeNodeFromObjectType(type: ObjectType): TypeNode { - if (type.objectFlags & ObjectFlags.Mapped) { - if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { - return createMappedTypeNodeFromType(type); - } + if (isGenericMappedType(type)) { + return createMappedTypeNodeFromType(type); } const resolved = resolveStructuredTypeMembers(type); @@ -3489,11 +3487,9 @@ namespace ts { } function writeLiteralType(type: ObjectType, flags: TypeFormatFlags) { - if (type.objectFlags & ObjectFlags.Mapped) { - if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { - writeMappedType(type); - return; - } + if (isGenericMappedType(type)) { + writeMappedType(type); + return; } const resolved = resolveStructuredTypeMembers(type); @@ -5792,8 +5788,7 @@ namespace ts { } function isGenericMappedType(type: Type) { - return getObjectFlags(type) & ObjectFlags.Mapped && - maybeTypeOfKind(getConstraintTypeFromMappedType(type), TypeFlags.TypeVariable | TypeFlags.Index); + return getObjectFlags(type) & ObjectFlags.Mapped && isGenericIndexType(getConstraintTypeFromMappedType(type)); } function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { @@ -7602,26 +7597,36 @@ namespace ts { return instantiateType(getTemplateTypeFromMappedType(type), templateMapper); } + function isGenericObjectType(type: Type): boolean { + return type.flags & TypeFlags.TypeVariable ? true : + getObjectFlags(type) & ObjectFlags.Mapped ? isGenericIndexType(getConstraintTypeFromMappedType(type)) : + type.flags & TypeFlags.UnionOrIntersection ? forEach((type).types, isGenericObjectType) : + false; + } + + function isGenericIndexType(type: Type): boolean { + return type.flags & (TypeFlags.TypeVariable | TypeFlags.Index) ? true : + type.flags & TypeFlags.UnionOrIntersection ? forEach((type).types, isGenericIndexType) : + false; + } + function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) { - // If the index type is generic, if the object type is generic and doesn't originate in an expression, - // or if the object type is a mapped type with a generic constraint, 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 (maybeTypeOfKind(indexType, TypeFlags.TypeVariable | TypeFlags.Index) || - maybeTypeOfKind(objectType, TypeFlags.TypeVariable) && !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) || - isGenericMappedType(objectType)) { + // 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"]' + // 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) { return objectType; } - // If the object type is a mapped type { [P in K]: E }, 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 we defer the operation by creating an indexed access type. + // Defer the operation by creating an indexed access type. const id = objectType.id + "," + indexType.id; let type = indexedAccessTypes.get(id); if (!type) {