diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 456d5b22325..26727e82c63 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -701,6 +701,11 @@ namespace ts { IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help } + const enum ContextFlags { + None = 0, + Signature = 1 << 0, // Obtaining contextual signature + } + const enum CallbackCheck { None, Bivariant, @@ -17751,7 +17756,7 @@ namespace ts { return undefined; } - function getContextualTypeForBinaryOperand(node: Expression): Type | undefined { + function getContextualTypeForBinaryOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined { const binaryExpression = node.parent; const { left, operatorToken, right } = binaryExpression; switch (operatorToken.kind) { @@ -17768,12 +17773,12 @@ namespace ts { // When an || expression has a contextual type, the operands are contextually typed by that type. When an || // expression has no contextual type, the right operand is contextually typed by the type of the left operand, // except for the special case of Javascript declarations of the form `namespace.prop = namespace.prop || {}` - const type = getContextualType(binaryExpression); + const type = getContextualType(binaryExpression, contextFlags); return !type && node === right && !isDefaultedExpandoInitializer(binaryExpression) ? getTypeOfExpression(left) : type; case SyntaxKind.AmpersandAmpersandToken: case SyntaxKind.CommaToken: - return node === right ? getContextualType(binaryExpression) : undefined; + return node === right ? getContextualType(binaryExpression, contextFlags) : undefined; default: return undefined; } @@ -17882,19 +17887,18 @@ namespace ts { // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one // exists. Otherwise, it is the type of the string index signature in T, if one exists. - function getContextualTypeForObjectLiteralMethod(node: MethodDeclaration): Type | undefined { + function getContextualTypeForObjectLiteralMethod(node: MethodDeclaration, contextFlags?: ContextFlags): Type | undefined { Debug.assert(isObjectLiteralMethod(node)); if (node.flags & NodeFlags.InWithStatement) { // We cannot answer semantic questions within a with block, do not proceed any further return undefined; } - - return getContextualTypeForObjectLiteralElement(node); + return getContextualTypeForObjectLiteralElement(node, contextFlags); } - function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike) { + function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike, contextFlags?: ContextFlags) { const objectLiteral = element.parent; - const type = getApparentTypeOfContextualType(objectLiteral); + const type = getApparentTypeOfContextualType(objectLiteral, contextFlags); if (type) { if (!hasNonBindableDynamicName(element)) { // For a (non-symbol) computed property, there is no reason to look up the name @@ -17906,11 +17910,9 @@ namespace ts { return propertyType; } } - return isNumericName(element.name!) && getIndexTypeOfContextualType(type, IndexKind.Number) || getIndexTypeOfContextualType(type, IndexKind.String); } - return undefined; } @@ -17925,9 +17927,9 @@ namespace ts { } // In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type. - function getContextualTypeForConditionalOperand(node: Expression): Type | undefined { + function getContextualTypeForConditionalOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined { const conditional = node.parent; - return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional) : undefined; + return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional, contextFlags) : undefined; } function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild) { @@ -18022,8 +18024,8 @@ 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 { - const contextualType = instantiateContextualType(getContextualType(node), node); + function getApparentTypeOfContextualType(node: Expression, contextFlags?: ContextFlags): Type | undefined { + const contextualType = instantiateContextualType(getContextualType(node, contextFlags), node, contextFlags); if (contextualType) { const apparentType = mapType(contextualType, getApparentType, /*noReductions*/ true); if (apparentType.flags & TypeFlags.Union) { @@ -18040,13 +18042,19 @@ namespace ts { // If the given contextual type contains 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 { + function instantiateContextualType(contextualType: Type | undefined, node: Expression, contextFlags?: ContextFlags): Type | undefined { if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) { const inferenceContext = getInferenceContext(node); - if (inferenceContext) { - if ((isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node)) && isContextSensitive(node)) { + // If no inferences have been made, nothing is gained from instantiating as type parameters + // would just be replaced with their defaults similar to the apparent type. + if (inferenceContext && some(inferenceContext.inferences, hasInferenceCandidates)) { + // For contextual signatures we incorporate all inferences made so far, e.g. from return + // types as well as arguments to the left in a function call. + if (contextFlags && contextFlags & ContextFlags.Signature) { return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper); } + // For other purposes (e.g. determining whether to produce literal types) we only + // incorporate inferences made from the return type in a function call. if (inferenceContext.returnMapper) { return instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); } @@ -18088,7 +18096,7 @@ namespace ts { * @param node the expression whose contextual type will be returned. * @returns the contextual type of an expression. */ - function getContextualType(node: Expression): Type | undefined { + function getContextualType(node: Expression, contextFlags?: ContextFlags): Type | undefined { if (node.flags & NodeFlags.InWithStatement) { // We cannot answer semantic questions within a with block, do not proceed any further return undefined; @@ -18118,26 +18126,26 @@ namespace ts { case SyntaxKind.AsExpression: return isConstTypeReference((parent).type) ? undefined : getTypeFromTypeNode((parent).type); case SyntaxKind.BinaryExpression: - return getContextualTypeForBinaryOperand(node); + return getContextualTypeForBinaryOperand(node, contextFlags); case SyntaxKind.PropertyAssignment: case SyntaxKind.ShorthandPropertyAssignment: - return getContextualTypeForObjectLiteralElement(parent); + return getContextualTypeForObjectLiteralElement(parent, contextFlags); case SyntaxKind.SpreadAssignment: - return getApparentTypeOfContextualType(parent.parent as ObjectLiteralExpression); + return getApparentTypeOfContextualType(parent.parent as ObjectLiteralExpression, contextFlags); case SyntaxKind.ArrayLiteralExpression: { const arrayLiteral = parent; - const type = getApparentTypeOfContextualType(arrayLiteral); + const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags); return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node)); } case SyntaxKind.ConditionalExpression: - return getContextualTypeForConditionalOperand(node); + return getContextualTypeForConditionalOperand(node, contextFlags); case SyntaxKind.TemplateSpan: Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); return getContextualTypeForSubstitutionExpression(parent.parent, node); case SyntaxKind.ParenthesizedExpression: { // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined; - return tag ? getTypeFromTypeNode(tag.typeExpression!.type) : getContextualType(parent); + return tag ? getTypeFromTypeNode(tag.typeExpression!.type) : getContextualType(parent, contextFlags); } case SyntaxKind.JsxExpression: return getContextualTypeForJsxExpression(parent); @@ -18328,12 +18336,6 @@ namespace ts { : undefined; } - function getContextualTypeForFunctionLikeDeclaration(node: FunctionExpression | ArrowFunction | MethodDeclaration) { - return isObjectLiteralMethod(node) ? - getContextualTypeForObjectLiteralMethod(node) : - getApparentTypeOfContextualType(node); - } - // Return the contextual signature for a given expression node. A contextual type provides a // contextual signature if it has a single call signature and if that call signature is non-generic. // If the contextual type is a union type, get the signature from each type possible and if they are @@ -18345,7 +18347,9 @@ namespace ts { if (typeTagSignature) { return typeTagSignature; } - const type = getContextualTypeForFunctionLikeDeclaration(node); + const type = isObjectLiteralMethod(node) ? + getContextualTypeForObjectLiteralMethod(node, ContextFlags.Signature) : + getApparentTypeOfContextualType(node, ContextFlags.Signature); if (!type) { return undefined; } @@ -20249,7 +20253,7 @@ namespace ts { return type; } - function getSpreadArgumentType(args: ReadonlyArray, index: number, argCount: number, restType: TypeParameter, context: InferenceContext | undefined) { + function getSpreadArgumentType(args: ReadonlyArray, index: number, argCount: number, restType: Type, context: InferenceContext | undefined) { if (index >= argCount - 1) { const arg = args[argCount - 1]; if (isSpreadArgument(arg)) { @@ -20260,7 +20264,7 @@ namespace ts { getArrayifiedType(checkExpressionWithContextualType((arg).expression, restType, context, CheckMode.Normal)); } } - const contextualType = getIndexTypeOfType(restType, IndexKind.Number) || anyType; + const contextualType = getIndexedAccessType(restType, numberType); const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index); const types = []; let spreadIndex = -1;