Skip to content

Commit c838b0c

Browse files
authored
Consult cached contextual types only when no contextFlags (#52611)
1 parent 1c9ce4f commit c838b0c

File tree

4 files changed

+165
-15
lines changed

4 files changed

+165
-15
lines changed

src/compiler/checker.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
21112111

21122112
const contextualTypeNodes: Node[] = [];
21132113
const contextualTypes: (Type | undefined)[] = [];
2114+
const contextualIsCache: boolean[] = [];
21142115
let contextualTypeCount = 0;
21152116

21162117
const inferenceContextNodes: Node[] = [];
@@ -19191,7 +19192,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1919119192
}
1919219193

1919319194
function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) {
19194-
pushContextualType(next, sourcePropType);
19195+
pushContextualType(next, sourcePropType, /*isCache*/ false);
1919519196
const result = checkExpressionForMutableLocation(next, CheckMode.Contextual);
1919619197
popContextualType();
1919719198
return result;
@@ -19508,7 +19509,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1950819509
}
1950919510
// recreate a tuple from the elements, if possible
1951019511
// Since we're re-doing the expression type, we need to reapply the contextual type
19511-
pushContextualType(node, target);
19512+
pushContextualType(node, target, /*isCache*/ false);
1951219513
const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true);
1951319514
popContextualType();
1951419515
if (isTupleLikeType(tupleizedType)) {
@@ -29034,10 +29035,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2903429035
// We cannot answer semantic questions within a with block, do not proceed any further
2903529036
return undefined;
2903629037
}
29037-
const index = findContextualNode(node);
29038+
// Cached contextual types are obtained with no ContextFlags, so we can only consult them for
29039+
// requests with no ContextFlags.
29040+
const index = findContextualNode(node, /*includeCaches*/ !contextFlags);
2903829041
if (index >= 0) {
29039-
const cached = contextualTypes[index];
29040-
if (cached || !contextFlags) return cached;
29042+
return contextualTypes[index];
2904129043
}
2904229044
const { parent } = node;
2904329045
switch (parent.kind) {
@@ -29110,19 +29112,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2911029112
return undefined;
2911129113
}
2911229114

29113-
function pushContextualType(node: Node, type: Type | undefined) {
29115+
function pushCachedContextualType(node: Expression) {
29116+
pushContextualType(node, getContextualType(node, /*contextFlags*/ undefined), /*isCache*/ true);
29117+
}
29118+
29119+
function pushContextualType(node: Expression, type: Type | undefined, isCache: boolean) {
2911429120
contextualTypeNodes[contextualTypeCount] = node;
2911529121
contextualTypes[contextualTypeCount] = type;
29122+
contextualIsCache[contextualTypeCount] = isCache;
2911629123
contextualTypeCount++;
2911729124
}
2911829125

2911929126
function popContextualType() {
2912029127
contextualTypeCount--;
2912129128
}
2912229129

29123-
function findContextualNode(node: Node) {
29130+
function findContextualNode(node: Node, includeCaches: boolean) {
2912429131
for (let i = contextualTypeCount - 1; i >= 0; i--) {
29125-
if (node === contextualTypeNodes[i]) {
29132+
if (node === contextualTypeNodes[i] && (includeCaches || !contextualIsCache[i])) {
2912629133
return i;
2912729134
}
2912829135
}
@@ -29149,7 +29156,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2914929156

2915029157
function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags: ContextFlags | undefined) {
2915129158
if (isJsxOpeningElement(node) && contextFlags !== ContextFlags.Completions) {
29152-
const index = findContextualNode(node.parent);
29159+
const index = findContextualNode(node.parent, /*includeCaches*/ !contextFlags);
2915329160
if (index >= 0) {
2915429161
// Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit
2915529162
// _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type
@@ -29485,7 +29492,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2948529492
const elementCount = elements.length;
2948629493
const elementTypes: Type[] = [];
2948729494
const elementFlags: ElementFlags[] = [];
29488-
pushContextualType(node, getContextualType(node, /*contextFlags*/ undefined));
29495+
pushCachedContextualType(node);
2948929496
const inDestructuringPattern = isAssignmentTarget(node);
2949029497
const inConstContext = isConstContext(node);
2949129498
const contextualType = getApparentTypeOfContextualType(node, /*contextFlags*/ undefined);
@@ -29668,7 +29675,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2966829675
let propertiesArray: Symbol[] = [];
2966929676
let spread: Type = emptyObjectType;
2967029677

29671-
pushContextualType(node, getContextualType(node, /*contextFlags*/ undefined));
29678+
pushCachedContextualType(node);
2967229679
const contextualType = getApparentTypeOfContextualType(node, /*contextFlags*/ undefined);
2967329680
const contextualTypeHasPattern = contextualType && contextualType.pattern &&
2967429681
(contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression);
@@ -36823,16 +36830,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3682336830
type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.StringLike));
3682436831
}
3682536832

36826-
function getContextNode(node: Expression): Node {
36827-
if (node.kind === SyntaxKind.JsxAttributes && !isJsxSelfClosingElement(node.parent)) {
36833+
function getContextNode(node: Expression): Expression {
36834+
if (isJsxAttributes(node) && !isJsxSelfClosingElement(node.parent)) {
3682836835
return node.parent.parent; // Needs to be the root JsxElement, so it encompasses the attributes _and_ the children (which are essentially part of the attributes)
3682936836
}
3683036837
return node;
3683136838
}
3683236839

3683336840
function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type {
3683436841
const contextNode = getContextNode(node);
36835-
pushContextualType(contextNode, contextualType);
36842+
pushContextualType(contextNode, contextualType, /*isCache*/ false);
3683636843
pushInferenceContext(contextNode, inferenceContext);
3683736844
const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0));
3683836845
// In CheckMode.Inferential we collect intra-expression inference sites to process before fixing any type
@@ -37223,7 +37230,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3722337230
if (links.contextFreeType) {
3722437231
return links.contextFreeType;
3722537232
}
37226-
pushContextualType(node, anyType);
37233+
pushContextualType(node, anyType, /*isCache*/ false);
3722737234
const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive);
3722837235
popContextualType();
3722937236
return type;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
=== tests/cases/compiler/contextualTypeCaching.ts ===
2+
// Repro from #52575
3+
4+
export interface Event<T> {
5+
>Event : Symbol(Event, Decl(contextualTypeCaching.ts, 0, 0))
6+
>T : Symbol(T, Decl(contextualTypeCaching.ts, 2, 23))
7+
8+
callback: (response: T) => void;
9+
>callback : Symbol(Event.callback, Decl(contextualTypeCaching.ts, 2, 27))
10+
>response : Symbol(response, Decl(contextualTypeCaching.ts, 3, 15))
11+
>T : Symbol(T, Decl(contextualTypeCaching.ts, 2, 23))
12+
13+
nested: {
14+
>nested : Symbol(Event.nested, Decl(contextualTypeCaching.ts, 3, 36))
15+
16+
nestedCallback: (response: T) => void;
17+
>nestedCallback : Symbol(nestedCallback, Decl(contextualTypeCaching.ts, 4, 13))
18+
>response : Symbol(response, Decl(contextualTypeCaching.ts, 5, 25))
19+
>T : Symbol(T, Decl(contextualTypeCaching.ts, 2, 23))
20+
}
21+
}
22+
23+
export type CustomEvents = {
24+
>CustomEvents : Symbol(CustomEvents, Decl(contextualTypeCaching.ts, 7, 1))
25+
26+
a: Event<string>
27+
>a : Symbol(a, Decl(contextualTypeCaching.ts, 9, 28))
28+
>Event : Symbol(Event, Decl(contextualTypeCaching.ts, 0, 0))
29+
30+
b: Event<number>
31+
>b : Symbol(b, Decl(contextualTypeCaching.ts, 10, 20))
32+
>Event : Symbol(Event, Decl(contextualTypeCaching.ts, 0, 0))
33+
34+
};
35+
36+
declare function emit<T extends keyof CustomEvents>(type: T, data: CustomEvents[T]): void
37+
>emit : Symbol(emit, Decl(contextualTypeCaching.ts, 12, 2))
38+
>T : Symbol(T, Decl(contextualTypeCaching.ts, 14, 22))
39+
>CustomEvents : Symbol(CustomEvents, Decl(contextualTypeCaching.ts, 7, 1))
40+
>type : Symbol(type, Decl(contextualTypeCaching.ts, 14, 52))
41+
>T : Symbol(T, Decl(contextualTypeCaching.ts, 14, 22))
42+
>data : Symbol(data, Decl(contextualTypeCaching.ts, 14, 60))
43+
>CustomEvents : Symbol(CustomEvents, Decl(contextualTypeCaching.ts, 7, 1))
44+
>T : Symbol(T, Decl(contextualTypeCaching.ts, 14, 22))
45+
46+
emit('a', {
47+
>emit : Symbol(emit, Decl(contextualTypeCaching.ts, 12, 2))
48+
49+
callback: (r) => {},
50+
>callback : Symbol(callback, Decl(contextualTypeCaching.ts, 16, 11))
51+
>r : Symbol(r, Decl(contextualTypeCaching.ts, 17, 15))
52+
53+
nested: {
54+
>nested : Symbol(nested, Decl(contextualTypeCaching.ts, 17, 24))
55+
56+
nestedCallback: (r) => {},
57+
>nestedCallback : Symbol(nestedCallback, Decl(contextualTypeCaching.ts, 18, 13))
58+
>r : Symbol(r, Decl(contextualTypeCaching.ts, 19, 25))
59+
60+
},
61+
});
62+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
=== tests/cases/compiler/contextualTypeCaching.ts ===
2+
// Repro from #52575
3+
4+
export interface Event<T> {
5+
callback: (response: T) => void;
6+
>callback : (response: T) => void
7+
>response : T
8+
9+
nested: {
10+
>nested : { nestedCallback: (response: T) => void; }
11+
12+
nestedCallback: (response: T) => void;
13+
>nestedCallback : (response: T) => void
14+
>response : T
15+
}
16+
}
17+
18+
export type CustomEvents = {
19+
>CustomEvents : { a: Event<string>; b: Event<number>; }
20+
21+
a: Event<string>
22+
>a : Event<string>
23+
24+
b: Event<number>
25+
>b : Event<number>
26+
27+
};
28+
29+
declare function emit<T extends keyof CustomEvents>(type: T, data: CustomEvents[T]): void
30+
>emit : <T extends keyof CustomEvents>(type: T, data: CustomEvents[T]) => void
31+
>type : T
32+
>data : CustomEvents[T]
33+
34+
emit('a', {
35+
>emit('a', { callback: (r) => {}, nested: { nestedCallback: (r) => {}, },}) : void
36+
>emit : <T extends keyof CustomEvents>(type: T, data: CustomEvents[T]) => void
37+
>'a' : "a"
38+
>{ callback: (r) => {}, nested: { nestedCallback: (r) => {}, },} : { callback: (r: string) => void; nested: { nestedCallback: (r: string) => void; }; }
39+
40+
callback: (r) => {},
41+
>callback : (r: string) => void
42+
>(r) => {} : (r: string) => void
43+
>r : string
44+
45+
nested: {
46+
>nested : { nestedCallback: (r: string) => void; }
47+
>{ nestedCallback: (r) => {}, } : { nestedCallback: (r: string) => void; }
48+
49+
nestedCallback: (r) => {},
50+
>nestedCallback : (r: string) => void
51+
>(r) => {} : (r: string) => void
52+
>r : string
53+
54+
},
55+
});
56+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
// Repro from #52575
5+
6+
export interface Event<T> {
7+
callback: (response: T) => void;
8+
nested: {
9+
nestedCallback: (response: T) => void;
10+
}
11+
}
12+
13+
export type CustomEvents = {
14+
a: Event<string>
15+
b: Event<number>
16+
};
17+
18+
declare function emit<T extends keyof CustomEvents>(type: T, data: CustomEvents[T]): void
19+
20+
emit('a', {
21+
callback: (r) => {},
22+
nested: {
23+
nestedCallback: (r) => {},
24+
},
25+
});

0 commit comments

Comments
 (0)