Skip to content

Commit bcb0da5

Browse files
committed
Add nested call and new expressions as potential intra expression inference sites
1 parent 04d4580 commit bcb0da5

11 files changed

+383
-31
lines changed

src/compiler/checker.ts

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,7 +1652,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
16521652
const node = getParseTreeNode(nodeIn, isJsxAttributeLike);
16531653
return node && getContextualTypeForJsxAttribute(node, /*contextFlags*/ undefined);
16541654
},
1655-
isContextSensitive,
1655+
containsContextSensitive,
16561656
getTypeOfPropertyOfContextualType,
16571657
getFullyQualifiedName,
16581658
getResolvedSignature: (node, candidatesOutArray, argumentCount) =>
@@ -6253,7 +6253,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
62536253
}
62546254

62556255
function symbolValueDeclarationIsContextSensitive(symbol: Symbol): boolean {
6256-
return symbol && !!symbol.valueDeclaration && isExpression(symbol.valueDeclaration) && !isContextSensitive(symbol.valueDeclaration);
6256+
return symbol && !!symbol.valueDeclaration && isExpression(symbol.valueDeclaration) && !containsContextSensitive(symbol.valueDeclaration);
62576257
}
62586258

62596259
function toNodeBuilderFlags(flags = TypeFormatFlags.None): NodeBuilderFlags {
@@ -19165,47 +19165,56 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1916519165
return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration);
1916619166
}
1916719167

19168-
// Returns true if the given expression contains (at any level of nesting) a function or arrow expression
19169-
// that is subject to contextual typing.
19170-
function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean {
19168+
function containsContextRelatedNode(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild, predicate: (node: Node) => boolean): boolean {
1917119169
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
1917219170
switch (node.kind) {
1917319171
case SyntaxKind.FunctionExpression:
1917419172
case SyntaxKind.ArrowFunction:
1917519173
case SyntaxKind.MethodDeclaration:
19176-
case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type
19177-
return isContextSensitiveFunctionLikeDeclaration(node as FunctionExpression | ArrowFunction | MethodDeclaration);
19174+
case SyntaxKind.FunctionDeclaration:
19175+
case SyntaxKind.CallExpression:
19176+
case SyntaxKind.NewExpression:
19177+
return predicate(node);
1917819178
case SyntaxKind.ObjectLiteralExpression:
19179-
return some((node as ObjectLiteralExpression).properties, isContextSensitive);
19179+
return some((node as ObjectLiteralExpression).properties, p => containsContextRelatedNode(p, predicate));
1918019180
case SyntaxKind.ArrayLiteralExpression:
19181-
return some((node as ArrayLiteralExpression).elements, isContextSensitive);
19181+
return some((node as ArrayLiteralExpression).elements, e => containsContextRelatedNode(e, predicate));
1918219182
case SyntaxKind.ConditionalExpression:
19183-
return isContextSensitive((node as ConditionalExpression).whenTrue) ||
19184-
isContextSensitive((node as ConditionalExpression).whenFalse);
19183+
return containsContextRelatedNode((node as ConditionalExpression).whenTrue, predicate) ||
19184+
containsContextRelatedNode((node as ConditionalExpression).whenFalse, predicate);
1918519185
case SyntaxKind.BinaryExpression:
1918619186
return ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken || (node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken) &&
19187-
(isContextSensitive((node as BinaryExpression).left) || isContextSensitive((node as BinaryExpression).right));
19187+
(containsContextRelatedNode((node as BinaryExpression).left, predicate) || containsContextRelatedNode((node as BinaryExpression).right, predicate));
1918819188
case SyntaxKind.PropertyAssignment:
19189-
return isContextSensitive((node as PropertyAssignment).initializer);
19189+
return containsContextRelatedNode((node as PropertyAssignment).initializer, predicate);
1919019190
case SyntaxKind.ParenthesizedExpression:
19191-
return isContextSensitive((node as ParenthesizedExpression).expression);
19191+
return containsContextRelatedNode((node as ParenthesizedExpression).expression, predicate);
1919219192
case SyntaxKind.JsxAttributes:
19193-
return some((node as JsxAttributes).properties, isContextSensitive) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, isContextSensitive);
19193+
return some((node as JsxAttributes).properties, p => containsContextRelatedNode(p, predicate)) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, c => containsContextRelatedNode(c, predicate));
1919419194
case SyntaxKind.JsxAttribute: {
19195-
// If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive.
1919619195
const { initializer } = node as JsxAttribute;
19197-
return !!initializer && isContextSensitive(initializer);
19196+
return !!initializer && containsContextRelatedNode(initializer, predicate);
1919819197
}
1919919198
case SyntaxKind.JsxExpression: {
1920019199
// It is possible to that node.expression is undefined (e.g <div x={} />)
1920119200
const { expression } = node as JsxExpression;
19202-
return !!expression && isContextSensitive(expression);
19201+
return !!expression && containsContextRelatedNode(expression, predicate);
1920319202
}
1920419203
}
1920519204

1920619205
return false;
1920719206
}
1920819207

19208+
// Returns true if the given expression contains (at any level of nesting) a function or arrow expression
19209+
// that is subject to contextual typing.
19210+
function containsContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean {
19211+
return containsContextRelatedNode(node, isContextSensitiveFunctionOrObjectLiteralMethod);
19212+
}
19213+
19214+
function containsContextSensitiveOrCallOrNewExpression(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean {
19215+
return containsContextRelatedNode(node, n => isContextSensitiveFunctionOrObjectLiteralMethod(n) || isCallOrNewExpression(n));
19216+
}
19217+
1920919218
function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
1921019219
return hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node);
1921119220
}
@@ -19215,9 +19224,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1921519224
return false;
1921619225
}
1921719226
if (node.body.kind !== SyntaxKind.Block) {
19218-
return isContextSensitive(node.body);
19227+
return containsContextSensitive(node.body);
1921919228
}
19220-
return !!forEachReturnStatement(node.body as Block, (statement) => !!statement.expression && isContextSensitive(statement.expression));
19229+
return !!forEachReturnStatement(node.body as Block, (statement) => !!statement.expression && containsContextSensitive(statement.expression));
1922119230
}
1922219231

1922319232
function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration {
@@ -29983,7 +29992,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2998329992
const type = checkExpressionForMutableLocation(e, checkMode, forceTuple);
2998429993
elementTypes.push(addOptionality(type, /*isProperty*/ true, hasOmittedExpression));
2998529994
elementFlags.push(hasOmittedExpression ? ElementFlags.Optional : ElementFlags.Required);
29986-
if (inTupleContext && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(e)) {
29995+
if (inTupleContext && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && containsContextSensitiveOrCallOrNewExpression(e)) {
2998729996
const inferenceContext = getInferenceContext(node);
2998829997
Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context
2998929998
addIntraExpressionInferenceSite(inferenceContext, e, type);
@@ -30208,8 +30217,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3020830217
member = prop;
3020930218
allPropertiesTable?.set(prop.escapedName, prop);
3021030219

30211-
if (contextualType && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) &&
30212-
(memberDecl.kind === SyntaxKind.PropertyAssignment || memberDecl.kind === SyntaxKind.MethodDeclaration) && isContextSensitive(memberDecl)) {
30220+
if (contextualType && checkMode && checkMode & CheckMode.Inferential && (!(checkMode & CheckMode.SkipContextSensitive) &&
30221+
(memberDecl.kind === SyntaxKind.PropertyAssignment || memberDecl.kind === SyntaxKind.MethodDeclaration) && containsContextSensitiveOrCallOrNewExpression(memberDecl))) {
3021330222
const inferenceContext = getInferenceContext(node);
3021430223
Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context
3021530224
const inferenceNode = memberDecl.kind === SyntaxKind.PropertyAssignment ? memberDecl.initializer : memberDecl;
@@ -30465,7 +30474,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3046530474
addDeprecatedSuggestion(attributeDecl.name, prop.declarations, attributeDecl.name.escapedText as string);
3046630475
}
3046730476
}
30468-
if (contextualType && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(attributeDecl)) {
30477+
if (contextualType && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && containsContextSensitiveOrCallOrNewExpression(attributeDecl)) {
3046930478
const inferenceContext = getInferenceContext(attributes);
3047030479
Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context
3047130480
const inferenceNode = (attributeDecl.initializer as JsxExpression).expression!;
@@ -33138,7 +33147,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3313833147
// For a decorator, no arguments are susceptible to contextual typing due to the fact
3313933148
// decorators are applied to a declaration by the emitter, and not to an expression.
3314033149
const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters;
33141-
let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal;
33150+
let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, containsContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal;
3314233151
argCheckMode |= checkMode & CheckMode.IsForStringLiteralArgumentCompletions;
3314333152

3314433153
// The following variables are captured and modified by calls to chooseOverload.
@@ -35865,7 +35874,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3586535874
}
3586635875

3586735876
// The identityMapper object is used to indicate that function expressions are wildcards
35868-
if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) {
35877+
if (checkMode && checkMode & CheckMode.SkipContextSensitive && containsContextSensitive(node)) {
3586935878
// Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage
3587035879
if (!getEffectiveReturnTypeNode(node) && !hasContextSensitiveParameters(node)) {
3587135880
// Return plain anyFunctionType if there is no possibility we'll make inferences from the return type
@@ -35910,7 +35919,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3591035919
if (!signature) {
3591135920
return;
3591235921
}
35913-
if (isContextSensitive(node)) {
35922+
if (containsContextSensitive(node)) {
3591435923
if (contextualSignature) {
3591535924
const inferenceContext = getInferenceContext(node);
3591635925
let instantiatedContextualSignature: Signature | undefined;

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5064,7 +5064,7 @@ export interface TypeChecker {
50645064
/** @internal */ getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike): Type | undefined;
50655065
/** @internal */ getContextualTypeForArgumentAtIndex(call: CallLikeExpression, argIndex: number): Type | undefined;
50665066
/** @internal */ getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined;
5067-
/** @internal */ isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike): boolean;
5067+
/** @internal */ containsContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike): boolean;
50685068
/** @internal */ getTypeOfPropertyOfContextualType(type: Type, name: __String): Type | undefined;
50695069

50705070
/**

src/services/refactors/extractSymbol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1357,7 +1357,7 @@ function extractConstantInScope(
13571357
: getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file);
13581358
const isJS = isInJSFile(scope);
13591359

1360-
let variableType = isJS || !checker.isContextSensitive(node)
1360+
let variableType = isJS || !checker.containsContextSensitive(node)
13611361
? undefined
13621362
: checker.typeToTypeNode(checker.getContextualType(node)!, scope, NodeBuilderFlags.NoTruncation); // TODO: GH#18217
13631363

tests/baselines/reference/intraExpressionInferences.errors.txt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,26 @@ tests/cases/conformance/types/typeRelationships/typeInference/intraExpressionInf
212212
let test1: "a" = u
213213
}
214214
})
215-
215+
216+
type ErrorFn = (error: unknown) => void;
217+
218+
declare const genericFn: <T>(args: {
219+
parser: (p: unknown, errorFn: ErrorFn) => T;
220+
handler: (data: { body: T }) => unknown;
221+
}) => T;
222+
223+
declare const createParser: <T>(arg: T) => (p: unknown, errorFn: ErrorFn) => T;
224+
225+
genericFn({
226+
parser: createParser(1 as const),
227+
handler: ({ body: _ }) => {},
228+
});
229+
230+
declare const genericFnTuple: <T>(
231+
args: [
232+
parser: (p: unknown, errorFn: ErrorFn) => T,
233+
handler: (data: { body: T }) => unknown
234+
]
235+
) => T;
236+
237+
genericFnTuple([createParser(1 as const), ({ body: _ }) => {}]);

tests/baselines/reference/intraExpressionInferences.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,29 @@ branch({
197197
let test1: "a" = u
198198
}
199199
})
200-
200+
201+
type ErrorFn = (error: unknown) => void;
202+
203+
declare const genericFn: <T>(args: {
204+
parser: (p: unknown, errorFn: ErrorFn) => T;
205+
handler: (data: { body: T }) => unknown;
206+
}) => T;
207+
208+
declare const createParser: <T>(arg: T) => (p: unknown, errorFn: ErrorFn) => T;
209+
210+
genericFn({
211+
parser: createParser(1 as const),
212+
handler: ({ body: _ }) => {},
213+
});
214+
215+
declare const genericFnTuple: <T>(
216+
args: [
217+
parser: (p: unknown, errorFn: ErrorFn) => T,
218+
handler: (data: { body: T }) => unknown
219+
]
220+
) => T;
221+
222+
genericFnTuple([createParser(1 as const), ({ body: _ }) => {}]);
201223

202224
//// [intraExpressionInferences.js]
203225
"use strict";
@@ -316,6 +338,15 @@ branch({
316338
var test1 = u;
317339
}
318340
});
341+
genericFn({
342+
parser: createParser(1),
343+
handler: function (_b) {
344+
var _ = _b.body;
345+
},
346+
});
347+
genericFnTuple([createParser(1), function (_b) {
348+
var _ = _b.body;
349+
}]);
319350

320351

321352
//// [intraExpressionInferences.d.ts]
@@ -383,3 +414,17 @@ declare const branch: <T, U extends T>(_: {
383414
then: (u: U) => void;
384415
}) => void;
385416
declare const x: "a" | "b";
417+
type ErrorFn = (error: unknown) => void;
418+
declare const genericFn: <T>(args: {
419+
parser: (p: unknown, errorFn: ErrorFn) => T;
420+
handler: (data: {
421+
body: T;
422+
}) => unknown;
423+
}) => T;
424+
declare const createParser: <T>(arg: T) => (p: unknown, errorFn: ErrorFn) => T;
425+
declare const genericFnTuple: <T>(args: [
426+
parser: (p: unknown, errorFn: ErrorFn) => T,
427+
handler: (data: {
428+
body: T;
429+
}) => unknown
430+
]) => T;

0 commit comments

Comments
 (0)