-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Add nested call and new expressions as potential intra expression inference sites #54183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
bcb0da5
fe969b9
45ffdfa
12b91f2
bfdaa86
9f023ac
299f264
668ccde
f92136f
32740a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1665,7 +1665,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), | ||
|
@@ -6428,7 +6428,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 { | ||
|
@@ -20006,47 +20006,56 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration); | ||
} | ||
|
||
// 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 <div x={} />) | ||
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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feels like you should consider the same of tagged template expressions (unless we currently don't handle those as direct arguments either. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I might be mistaken but I think the above is true today: function test<T extends TemplateStringsArray>(a: T) {}
test`foo`
// ^? function test<TemplateStringsArray>(a: TemplateStringsArray): void |
||
} | ||
|
||
function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { | ||
return hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node); | ||
} | ||
|
@@ -20056,9 +20065,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 { | ||
|
@@ -31379,7 +31388,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); | ||
|
@@ -31614,7 +31623,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 | ||
|
@@ -31873,7 +31882,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!; | ||
|
@@ -34595,7 +34604,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; | ||
let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal; | ||
let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, containsContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal; | ||
|
||
// The following variables are captured and modified by calls to chooseOverload. | ||
// If overload resolution or type argument inference fails, we want to report the | ||
|
@@ -37409,7 +37418,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 | ||
|
@@ -37454,7 +37463,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; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I wonder what the technical reasons are around why this is this still needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the question why this is just not replaced by
containsContextSensitiveOrCallOrNewExpression
everywhere?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah exactly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tbh, I don't know ;p it has been almost a year since I opened this PR. I'm not sure if there was any specific reason behind this choice or if I just tried to minimize the surface area of this change. I can run an experiment on this later to learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I ran this experiment and here is the diff
git diff
So nothing spectacular has changed here. However, it shows that couple of caches get bigger after this change. The change in the inferred type in
reverseMappedTypeContextualTypeNotCircular
is slightly worrying but did some extra experiments with the code like this that doesn't error (this test case is meant to error):Even though some of those seemingly regressed (
any
inferred in some of them)... I'm not that sure if that would be the correct assessment of what happens here.At the moment, inference sources for type variables used within reverse-mapped types are often "ignored". See the corresponding issue and some of my attempts to address the situation here. You can also see some extra instances of how it behaves today weirdly and somewhat inconsistently in my recent comment here.
So to me, it seems that this change just somehow makes such functions with calls/news at the return positions viable inference sources for other type variables