diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f29e12ff288ba..83acddf63d76e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2110,6 +2110,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const contextualTypeNodes: Node[] = []; const contextualTypes: (Type | undefined)[] = []; + const contextualIsCache: boolean[] = []; let contextualTypeCount = 0; const inferenceContextNodes: Node[] = []; @@ -19199,7 +19200,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) { - pushContextualType(next, sourcePropType); + pushContextualType(next, sourcePropType, /*isCache*/ false); const result = checkExpressionForMutableLocation(next, CheckMode.Contextual); popContextualType(); return result; @@ -19516,7 +19517,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } // recreate a tuple from the elements, if possible // Since we're re-doing the expression type, we need to reapply the contextual type - pushContextualType(node, target); + pushContextualType(node, target, /*isCache*/ false); const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); popContextualType(); if (isTupleLikeType(tupleizedType)) { @@ -29035,10 +29036,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // We cannot answer semantic questions within a with block, do not proceed any further return undefined; } - const index = findContextualNode(node); + // Cached contextual types are obtained with no ContextFlags, so we can only consult them for + // requests with no ContextFlags. + const index = findContextualNode(node, /*includeCaches*/ !contextFlags); if (index >= 0) { - const cached = contextualTypes[index]; - if (cached || !contextFlags) return cached; + return contextualTypes[index]; } const { parent } = node; switch (parent.kind) { @@ -29111,9 +29113,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return undefined; } - function pushContextualType(node: Node, type: Type | undefined) { + function pushCachedContextualType(node: Expression) { + pushContextualType(node, getContextualType(node, /*contextFlags*/ undefined), /*isCache*/ true); + } + + function pushContextualType(node: Expression, type: Type | undefined, isCache: boolean) { contextualTypeNodes[contextualTypeCount] = node; contextualTypes[contextualTypeCount] = type; + contextualIsCache[contextualTypeCount] = isCache; contextualTypeCount++; } @@ -29121,9 +29128,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { contextualTypeCount--; } - function findContextualNode(node: Node) { + function findContextualNode(node: Node, includeCaches: boolean) { for (let i = contextualTypeCount - 1; i >= 0; i--) { - if (node === contextualTypeNodes[i]) { + if (node === contextualTypeNodes[i] && (includeCaches || !contextualIsCache[i])) { return i; } } @@ -29150,7 +29157,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags: ContextFlags | undefined) { if (isJsxOpeningElement(node) && contextFlags !== ContextFlags.Completions) { - const index = findContextualNode(node.parent); + const index = findContextualNode(node.parent, /*includeCaches*/ !contextFlags); if (index >= 0) { // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit // _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type @@ -29486,7 +29493,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const elementCount = elements.length; const elementTypes: Type[] = []; const elementFlags: ElementFlags[] = []; - pushContextualType(node, getContextualType(node, /*contextFlags*/ undefined)); + pushCachedContextualType(node); const inDestructuringPattern = isAssignmentTarget(node); const inConstContext = isConstContext(node); const contextualType = getApparentTypeOfContextualType(node, /*contextFlags*/ undefined); @@ -29669,7 +29676,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let propertiesArray: Symbol[] = []; let spread: Type = emptyObjectType; - pushContextualType(node, getContextualType(node, /*contextFlags*/ undefined)); + pushCachedContextualType(node); const contextualType = getApparentTypeOfContextualType(node, /*contextFlags*/ undefined); const contextualTypeHasPattern = contextualType && contextualType.pattern && (contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression); @@ -36828,8 +36835,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.StringLike)); } - function getContextNode(node: Expression): Node { - if (node.kind === SyntaxKind.JsxAttributes && !isJsxSelfClosingElement(node.parent)) { + function getContextNode(node: Expression): Expression { + if (isJsxAttributes(node) && !isJsxSelfClosingElement(node.parent)) { 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) } return node; @@ -36837,7 +36844,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { const contextNode = getContextNode(node); - pushContextualType(contextNode, contextualType); + pushContextualType(contextNode, contextualType, /*isCache*/ false); pushInferenceContext(contextNode, inferenceContext); const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); // In CheckMode.Inferential we collect intra-expression inference sites to process before fixing any type @@ -37228,7 +37235,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (links.contextFreeType) { return links.contextFreeType; } - pushContextualType(node, anyType); + pushContextualType(node, anyType, /*isCache*/ false); const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); popContextualType(); return type; diff --git a/tests/baselines/reference/contextualTypeCaching.symbols b/tests/baselines/reference/contextualTypeCaching.symbols new file mode 100644 index 0000000000000..24122b7afddd5 --- /dev/null +++ b/tests/baselines/reference/contextualTypeCaching.symbols @@ -0,0 +1,62 @@ +=== tests/cases/compiler/contextualTypeCaching.ts === +// Repro from #52575 + +export interface Event { +>Event : Symbol(Event, Decl(contextualTypeCaching.ts, 0, 0)) +>T : Symbol(T, Decl(contextualTypeCaching.ts, 2, 23)) + + callback: (response: T) => void; +>callback : Symbol(Event.callback, Decl(contextualTypeCaching.ts, 2, 27)) +>response : Symbol(response, Decl(contextualTypeCaching.ts, 3, 15)) +>T : Symbol(T, Decl(contextualTypeCaching.ts, 2, 23)) + + nested: { +>nested : Symbol(Event.nested, Decl(contextualTypeCaching.ts, 3, 36)) + + nestedCallback: (response: T) => void; +>nestedCallback : Symbol(nestedCallback, Decl(contextualTypeCaching.ts, 4, 13)) +>response : Symbol(response, Decl(contextualTypeCaching.ts, 5, 25)) +>T : Symbol(T, Decl(contextualTypeCaching.ts, 2, 23)) + } +} + +export type CustomEvents = { +>CustomEvents : Symbol(CustomEvents, Decl(contextualTypeCaching.ts, 7, 1)) + + a: Event +>a : Symbol(a, Decl(contextualTypeCaching.ts, 9, 28)) +>Event : Symbol(Event, Decl(contextualTypeCaching.ts, 0, 0)) + + b: Event +>b : Symbol(b, Decl(contextualTypeCaching.ts, 10, 20)) +>Event : Symbol(Event, Decl(contextualTypeCaching.ts, 0, 0)) + +}; + +declare function emit(type: T, data: CustomEvents[T]): void +>emit : Symbol(emit, Decl(contextualTypeCaching.ts, 12, 2)) +>T : Symbol(T, Decl(contextualTypeCaching.ts, 14, 22)) +>CustomEvents : Symbol(CustomEvents, Decl(contextualTypeCaching.ts, 7, 1)) +>type : Symbol(type, Decl(contextualTypeCaching.ts, 14, 52)) +>T : Symbol(T, Decl(contextualTypeCaching.ts, 14, 22)) +>data : Symbol(data, Decl(contextualTypeCaching.ts, 14, 60)) +>CustomEvents : Symbol(CustomEvents, Decl(contextualTypeCaching.ts, 7, 1)) +>T : Symbol(T, Decl(contextualTypeCaching.ts, 14, 22)) + +emit('a', { +>emit : Symbol(emit, Decl(contextualTypeCaching.ts, 12, 2)) + + callback: (r) => {}, +>callback : Symbol(callback, Decl(contextualTypeCaching.ts, 16, 11)) +>r : Symbol(r, Decl(contextualTypeCaching.ts, 17, 15)) + + nested: { +>nested : Symbol(nested, Decl(contextualTypeCaching.ts, 17, 24)) + + nestedCallback: (r) => {}, +>nestedCallback : Symbol(nestedCallback, Decl(contextualTypeCaching.ts, 18, 13)) +>r : Symbol(r, Decl(contextualTypeCaching.ts, 19, 25)) + + }, +}); + diff --git a/tests/baselines/reference/contextualTypeCaching.types b/tests/baselines/reference/contextualTypeCaching.types new file mode 100644 index 0000000000000..16c99d04e16d2 --- /dev/null +++ b/tests/baselines/reference/contextualTypeCaching.types @@ -0,0 +1,56 @@ +=== tests/cases/compiler/contextualTypeCaching.ts === +// Repro from #52575 + +export interface Event { + callback: (response: T) => void; +>callback : (response: T) => void +>response : T + + nested: { +>nested : { nestedCallback: (response: T) => void; } + + nestedCallback: (response: T) => void; +>nestedCallback : (response: T) => void +>response : T + } +} + +export type CustomEvents = { +>CustomEvents : { a: Event; b: Event; } + + a: Event +>a : Event + + b: Event +>b : Event + +}; + +declare function emit(type: T, data: CustomEvents[T]): void +>emit : (type: T, data: CustomEvents[T]) => void +>type : T +>data : CustomEvents[T] + +emit('a', { +>emit('a', { callback: (r) => {}, nested: { nestedCallback: (r) => {}, },}) : void +>emit : (type: T, data: CustomEvents[T]) => void +>'a' : "a" +>{ callback: (r) => {}, nested: { nestedCallback: (r) => {}, },} : { callback: (r: string) => void; nested: { nestedCallback: (r: string) => void; }; } + + callback: (r) => {}, +>callback : (r: string) => void +>(r) => {} : (r: string) => void +>r : string + + nested: { +>nested : { nestedCallback: (r: string) => void; } +>{ nestedCallback: (r) => {}, } : { nestedCallback: (r: string) => void; } + + nestedCallback: (r) => {}, +>nestedCallback : (r: string) => void +>(r) => {} : (r: string) => void +>r : string + + }, +}); + diff --git a/tests/cases/compiler/contextualTypeCaching.ts b/tests/cases/compiler/contextualTypeCaching.ts new file mode 100644 index 0000000000000..542a98724e56a --- /dev/null +++ b/tests/cases/compiler/contextualTypeCaching.ts @@ -0,0 +1,25 @@ +// @strict: true +// @noEmit: true + +// Repro from #52575 + +export interface Event { + callback: (response: T) => void; + nested: { + nestedCallback: (response: T) => void; + } +} + +export type CustomEvents = { + a: Event + b: Event +}; + +declare function emit(type: T, data: CustomEvents[T]): void + +emit('a', { + callback: (r) => {}, + nested: { + nestedCallback: (r) => {}, + }, +});