From 52a9cfb0a9de3f0c0f36a77bb976330a0a57f6df Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 6 Feb 2019 14:23:49 -0800 Subject: [PATCH] Infer to partially homomorphic mapped types (such as Pick) --- src/compiler/checker.ts | 45 ++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ce06e84b2fd..aa5007065fc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14570,11 +14570,24 @@ namespace ts { return undefined; } - function inferFromMappedTypeConstraint(source: Type, target: Type, constraintType: Type): boolean { + function inferToHomomorphicMappedType(source: Type, target: MappedType, constraintType: IndexType) { + const inference = getInferenceInfoForType(constraintType.type); + if (inference && !inference.isFixed) { + const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType); + if (inferredType) { + const savePriority = priority; + priority |= InferencePriority.HomomorphicMappedType; + inferFromTypes(inferredType, inference.typeParameter); + priority = savePriority; + } + } + } + + function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { if (constraintType.flags & TypeFlags.Union) { let result = false; for (const type of (constraintType as UnionType).types) { - result = inferFromMappedTypeConstraint(source, target, type) || result; + result = inferToMappedType(source, target, type) || result; } return result; } @@ -14583,31 +14596,31 @@ namespace ts { // where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source // type and then make a secondary inference from that type to T. We make a secondary inference // such that direct inferences to T get priority over inferences to Partial, for example. - const inference = getInferenceInfoForType((constraintType).type); - if (inference && !inference.isFixed) { - const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType as IndexType); - if (inferredType) { - const savePriority = priority; - priority |= InferencePriority.HomomorphicMappedType; - inferFromTypes(inferredType, inference.typeParameter); - priority = savePriority; - } - } + inferToHomomorphicMappedType(source, target, constraintType); return true; } if (constraintType.flags & TypeFlags.TypeParameter) { - // We're inferring from some source type S to a mapped type { [P in T]: X }, where T is a type - // parameter. Infer from 'keyof S' to T and infer from a union of each property type in S to X. + // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type + // parameter. First infer from 'keyof S' to K. const savePriority = priority; priority |= InferencePriority.MappedTypeConstraint; inferFromTypes(getIndexType(source), constraintType); priority = savePriority; + // If K is constrained to an index type keyof T, where T is a type parameter, proceed to make + // the same inferences as we would for a homomorphic mapped type { [P in keyof T]: X } (this + // enables us to make meaningful inferences when the target is a Pick). Otherwise, infer + // from a union of the property types in the source to the template type X. + const extendedConstraint = getConstraintOfType(constraintType); + if (extendedConstraint && extendedConstraint.flags & TypeFlags.Index) { + inferToHomomorphicMappedType(source, target, extendedConstraint); + return true; + } const valueTypes = compact([ getIndexTypeOfType(source, IndexKind.String), getIndexTypeOfType(source, IndexKind.Number), ...map(getPropertiesOfType(source), getTypeOfSymbol) ]); - inferFromTypes(getUnionType(valueTypes), getTemplateTypeFromMappedType(target)); + inferFromTypes(getUnionType(valueTypes), getTemplateTypeFromMappedType(target)); return true; } return false; @@ -14622,7 +14635,7 @@ namespace ts { } if (getObjectFlags(target) & ObjectFlags.Mapped) { const constraintType = getConstraintTypeFromMappedType(target); - if (inferFromMappedTypeConstraint(source, target, constraintType)) { + if (inferToMappedType(source, target, constraintType)) { return; } }