diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 75be36474222f..a5a7529b48931 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1758,7 +1758,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const node = getParseTreeNode(nodeIn, isJsxAttributeLike); return node && getContextualTypeForJsxAttribute(node, /*contextFlags*/ undefined); }, - isContextSensitive, + containsContextSensitive, getTypeOfPropertyOfContextualType, getFullyQualifiedName, getResolvedSignature: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), @@ -6213,7 +6213,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function symbolValueDeclarationIsContextSensitive(symbol: Symbol): boolean { - return symbol && !!symbol.valueDeclaration && isExpression(symbol.valueDeclaration) && !isContextSensitive(symbol.valueDeclaration); + return symbol && !!symbol.valueDeclaration && isExpression(symbol.valueDeclaration) && !containsContextSensitive(symbol.valueDeclaration); } function toNodeBuilderFlags(flags = TypeFormatFlags.None): NodeBuilderFlags { @@ -13000,7 +13000,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.MappedType: case SyntaxKind.ConditionalType: { const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); - if ((kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction || isObjectLiteralMethod(node)) && isContextSensitive(node as Expression | MethodDeclaration)) { + if ((kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction || isObjectLiteralMethod(node)) && containsContextSensitive(node as Expression | MethodDeclaration)) { const signature = firstOrUndefined(getSignaturesOfType(getTypeOfSymbol(getSymbolOfDeclaration(node as FunctionLikeDeclaration)), SignatureKind.Call)); if (signature && signature.typeParameters) { return [...(outerTypeParameters || emptyArray), ...signature.typeParameters]; @@ -21104,47 +21104,56 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration, info.components); } - // Returns true if the given expression contains (at any level of nesting) a function or arrow expression - // that is subject to contextual typing. - function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { + function containsContextRelatedNode(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild, predicate: (node: Node) => boolean): boolean { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); switch (node.kind) { case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type - return isContextSensitiveFunctionLikeDeclaration(node as FunctionExpression | ArrowFunction | MethodDeclaration); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + return predicate(node); case SyntaxKind.ObjectLiteralExpression: - return some((node as ObjectLiteralExpression).properties, isContextSensitive); + return some((node as ObjectLiteralExpression).properties, p => containsContextRelatedNode(p, predicate)); case SyntaxKind.ArrayLiteralExpression: - return some((node as ArrayLiteralExpression).elements, isContextSensitive); + return some((node as ArrayLiteralExpression).elements, e => containsContextRelatedNode(e, predicate)); case SyntaxKind.ConditionalExpression: - return isContextSensitive((node as ConditionalExpression).whenTrue) || - isContextSensitive((node as ConditionalExpression).whenFalse); + return containsContextRelatedNode((node as ConditionalExpression).whenTrue, predicate) || + containsContextRelatedNode((node as ConditionalExpression).whenFalse, predicate); case SyntaxKind.BinaryExpression: return ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken || (node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken) && - (isContextSensitive((node as BinaryExpression).left) || isContextSensitive((node as BinaryExpression).right)); + (containsContextRelatedNode((node as BinaryExpression).left, predicate) || containsContextRelatedNode((node as BinaryExpression).right, predicate)); case SyntaxKind.PropertyAssignment: - return isContextSensitive((node as PropertyAssignment).initializer); + return containsContextRelatedNode((node as PropertyAssignment).initializer, predicate); case SyntaxKind.ParenthesizedExpression: - return isContextSensitive((node as ParenthesizedExpression).expression); + return containsContextRelatedNode((node as ParenthesizedExpression).expression, predicate); case SyntaxKind.JsxAttributes: - return some((node as JsxAttributes).properties, isContextSensitive) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, isContextSensitive); + return some((node as JsxAttributes).properties, p => containsContextRelatedNode(p, predicate)) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, c => containsContextRelatedNode(c, predicate)); case SyntaxKind.JsxAttribute: { - // If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive. const { initializer } = node as JsxAttribute; - return !!initializer && isContextSensitive(initializer); + return !!initializer && containsContextRelatedNode(initializer, predicate); } case SyntaxKind.JsxExpression: { // It is possible to that node.expression is undefined (e.g
) const { expression } = node as JsxExpression; - return !!expression && isContextSensitive(expression); + return !!expression && containsContextRelatedNode(expression, predicate); } } return false; } + // Returns true if the given expression contains (at any level of nesting) a function or arrow expression + // that is subject to contextual typing. + function containsContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { + return containsContextRelatedNode(node, isContextSensitiveFunctionOrObjectLiteralMethod); + } + + function containsContextSensitiveOrCallOrNewExpression(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { + return containsContextRelatedNode(node, n => isContextSensitiveFunctionOrObjectLiteralMethod(n) || isCallOrNewExpression(n)); + } + function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { return hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node); } @@ -21154,9 +21163,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; } if (node.body.kind !== SyntaxKind.Block) { - return isContextSensitive(node.body); + return containsContextSensitive(node.body); } - return !!forEachReturnStatement(node.body as Block, statement => !!statement.expression && isContextSensitive(statement.expression)); + return !!forEachReturnStatement(node.body as Block, statement => !!statement.expression && containsContextSensitive(statement.expression)); } function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { @@ -33264,7 +33273,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const type = checkExpressionForMutableLocation(e, checkMode, forceTuple); elementTypes.push(addOptionality(type, /*isProperty*/ true, hasOmittedExpression)); elementFlags.push(hasOmittedExpression ? ElementFlags.Optional : ElementFlags.Required); - if (inTupleContext && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(e)) { + if (inTupleContext && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && containsContextSensitiveOrCallOrNewExpression(e)) { const inferenceContext = getInferenceContext(node); Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context addIntraExpressionInferenceSite(inferenceContext, e, type); @@ -33505,7 +33514,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if ( contextualType && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && - (memberDecl.kind === SyntaxKind.PropertyAssignment || memberDecl.kind === SyntaxKind.MethodDeclaration) && isContextSensitive(memberDecl) + (memberDecl.kind === SyntaxKind.PropertyAssignment || memberDecl.kind === SyntaxKind.MethodDeclaration) && containsContextSensitiveOrCallOrNewExpression(memberDecl) ) { const inferenceContext = getInferenceContext(node); Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context @@ -33744,7 +33753,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { addDeprecatedSuggestion(attributeDecl.name, prop.declarations, attributeDecl.name.escapedText as string); } } - if (contextualType && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(attributeDecl)) { + if (contextualType && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && containsContextSensitiveOrCallOrNewExpression(attributeDecl)) { const inferenceContext = getInferenceContext(attributes); Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context const inferenceNode = (attributeDecl.initializer as JsxExpression).expression!; @@ -36507,7 +36516,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // For a decorator, no arguments are susceptible to contextual typing due to the fact // decorators are applied to a declaration by the emitter, and not to an expression. const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters; - if (!isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive)) { + if (!isDecorator && !isSingleNonGenericCandidate && some(args, containsContextSensitive)) { argCheckMode = CheckMode.SkipContextSensitive; } @@ -39494,7 +39503,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } // The identityMapper object is used to indicate that function expressions are wildcards - if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) { + if (checkMode && checkMode & CheckMode.SkipContextSensitive && containsContextSensitive(node)) { // Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage if (!getEffectiveReturnTypeNode(node) && !hasContextSensitiveParameters(node)) { // Return plain anyFunctionType if there is no possibility we'll make inferences from the return type @@ -39539,7 +39548,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!signature) { return; } - if (isContextSensitive(node)) { + if (containsContextSensitive(node)) { if (contextualSignature) { const inferenceContext = getInferenceContext(node); let instantiatedContextualSignature: Signature | undefined; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1cfe3e04ba68d..2cef5a142af76 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5238,7 +5238,7 @@ export interface TypeChecker { /** @internal */ getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike): Type | undefined; /** @internal */ getContextualTypeForArgumentAtIndex(call: CallLikeExpression, argIndex: number): Type | undefined; /** @internal */ getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined; - /** @internal */ isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike): boolean; + /** @internal */ containsContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike): boolean; /** @internal */ getTypeOfPropertyOfContextualType(type: Type, name: __String): Type | undefined; /** diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 2d59e42c433d6..b4fa0cecaf29f 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -1379,7 +1379,7 @@ function extractConstantInScope( const localNameText = getIdentifierForNode(node, scope, checker, file); const isJS = isInJSFile(scope); - let variableType = isJS || !checker.isContextSensitive(node) + let variableType = isJS || !checker.containsContextSensitive(node) ? undefined : checker.typeToTypeNode(checker.getContextualType(node)!, scope, NodeBuilderFlags.NoTruncation, InternalNodeBuilderFlags.AllowUnresolvedNames); // TODO: GH#18217 diff --git a/tests/baselines/reference/intraExpressionInferences.errors.txt b/tests/baselines/reference/intraExpressionInferences.errors.txt index 82da18c788e9b..5e29182af2f2a 100644 --- a/tests/baselines/reference/intraExpressionInferences.errors.txt +++ b/tests/baselines/reference/intraExpressionInferences.errors.txt @@ -344,4 +344,27 @@ intraExpressionInferences.ts(133,26): error TS2339: Property 'nonexistent' does }, consumer: (val) => {}, }); + + type ErrorFn = (error: unknown) => void; + + declare const genericFn: