From 1b4bce236eeb2dc6ed0f3a5467b15f5bedeb2dfa Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 17 Jan 2019 14:37:31 -0800 Subject: [PATCH] Infer contextual types from return types through generic functions --- src/compiler/checker.ts | 46 +++++++++++++++++++++++++++-------------- src/compiler/types.ts | 1 + 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cb132ebf927..352a5c31592 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14591,18 +14591,18 @@ namespace ts { } function inferFromProperties(source: Type, target: Type) { - if (isTupleType(source)) { + if (isArrayType(source) || isTupleType(source)) { if (isTupleType(target)) { - const sourceLength = getLengthOfTupleType(source); + const sourceLength = isTupleType(source) ? getLengthOfTupleType(source) : 0; const targetLength = getLengthOfTupleType(target); - const sourceRestType = getRestTypeOfTupleType(source); + const sourceRestType = isTupleType(source) ? getRestTypeOfTupleType(source) : getElementTypeOfArrayType(source); const targetRestType = getRestTypeOfTupleType(target); const fixedLength = targetLength < sourceLength || sourceRestType ? targetLength : sourceLength; for (let i = 0; i < fixedLength; i++) { - inferFromTypes(i < sourceLength ? source.typeArguments![i] : sourceRestType!, target.typeArguments![i]); + inferFromTypes(i < sourceLength ? (source).typeArguments![i] : sourceRestType!, target.typeArguments![i]); } if (targetRestType) { - const types = fixedLength < sourceLength ? source.typeArguments!.slice(fixedLength, sourceLength) : []; + const types = fixedLength < sourceLength ? (source).typeArguments!.slice(fixedLength, sourceLength) : []; if (sourceRestType) { types.push(sourceRestType); } @@ -17694,14 +17694,28 @@ namespace ts { // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily // be "pushed" onto a node using the contextualType property. function getApparentTypeOfContextualType(node: Expression): Type | undefined { - let contextualType = getContextualType(node); - contextualType = contextualType && mapType(contextualType, getApparentType); - if (contextualType && contextualType.flags & TypeFlags.Union) { - if (isObjectLiteralExpression(node)) { - return discriminateContextualTypeByObjectMembers(node, contextualType as UnionType); + const contextualType = instantiateContextualType(getContextualType(node), node); + if (contextualType) { + const apparentType = mapType(contextualType, getApparentType); + if (apparentType.flags & TypeFlags.Union) { + if (isObjectLiteralExpression(node)) { + return discriminateContextualTypeByObjectMembers(node, apparentType as UnionType); + } + else if (isJsxAttributes(node)) { + return discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType); + } } - else if (isJsxAttributes(node)) { - return discriminateContextualTypeByJSXAttributes(node, contextualType as UnionType); + return apparentType; + } + } + + // If the given contextual type constains instantiable types and if a mapper representing + // return type inferences is available, instantiate those types using that mapper. + function instantiateContextualType(contextualType: Type | undefined, node: Expression): Type | undefined { + if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) { + const returnMapper = (getContextualMapper(node)).returnMapper; + if (returnMapper) { + return mapType(contextualType, t => t.flags & TypeFlags.Instantiable ? instantiateType(t, returnMapper) : t); } } return contextualType; @@ -19847,6 +19861,9 @@ namespace ts { const inferenceTargetType = getReturnTypeOfSignature(signature); // Inferences made from return types have lower priority than all other inferences. inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); + // Create a type mapper for instantiating generic contextual types using the inferences made + // from the return type. + context.returnMapper = cloneTypeMapper(context); } } @@ -23022,12 +23039,9 @@ namespace ts { } function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, contextualType?: Type, forceTuple?: boolean): Type { - if (arguments.length === 2) { - contextualType = getContextualType(node); - } const type = checkExpression(node, checkMode, forceTuple); return isTypeAssertion(node) ? type : - getWidenedLiteralLikeTypeForContextualType(type, contextualType); + getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node) : contextualType, node)); } function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c6c34c7434e..a2e1fe73798 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4364,6 +4364,7 @@ namespace ts { inferences: InferenceInfo[]; // Inferences made for each type parameter flags: InferenceFlags; // Inference flags compareTypes: TypeComparer; // Type comparer function + returnMapper?: TypeMapper; // Type mapper for inferences from return types (if any) } /* @internal */