Consult cached contextual types only when no contextFlags (#52611)

This commit is contained in:
Anders Hejlsberg 2023-02-10 11:17:59 -08:00 committed by GitHub
parent 1c9ce4fc2e
commit c838b0cb7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 165 additions and 15 deletions

View File

@ -2111,6 +2111,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const contextualTypeNodes: Node[] = [];
const contextualTypes: (Type | undefined)[] = [];
const contextualIsCache: boolean[] = [];
let contextualTypeCount = 0;
const inferenceContextNodes: Node[] = [];
@ -19191,7 +19192,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;
@ -19508,7 +19509,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)) {
@ -29034,10 +29035,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) {
@ -29110,9 +29112,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++;
}
@ -29120,9 +29127,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;
}
}
@ -29149,7 +29156,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
@ -29485,7 +29492,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);
@ -29668,7 +29675,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);
@ -36823,8 +36830,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;
@ -36832,7 +36839,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
@ -37223,7 +37230,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;

View File

@ -0,0 +1,62 @@
=== tests/cases/compiler/contextualTypeCaching.ts ===
// Repro from #52575
export interface Event<T> {
>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<string>
>a : Symbol(a, Decl(contextualTypeCaching.ts, 9, 28))
>Event : Symbol(Event, Decl(contextualTypeCaching.ts, 0, 0))
b: Event<number>
>b : Symbol(b, Decl(contextualTypeCaching.ts, 10, 20))
>Event : Symbol(Event, Decl(contextualTypeCaching.ts, 0, 0))
};
declare function emit<T extends keyof CustomEvents>(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))
},
});

View File

@ -0,0 +1,56 @@
=== tests/cases/compiler/contextualTypeCaching.ts ===
// Repro from #52575
export interface Event<T> {
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<string>; b: Event<number>; }
a: Event<string>
>a : Event<string>
b: Event<number>
>b : Event<number>
};
declare function emit<T extends keyof CustomEvents>(type: T, data: CustomEvents[T]): void
>emit : <T extends keyof CustomEvents>(type: T, data: CustomEvents[T]) => void
>type : T
>data : CustomEvents[T]
emit('a', {
>emit('a', { callback: (r) => {}, nested: { nestedCallback: (r) => {}, },}) : void
>emit : <T extends keyof CustomEvents>(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
},
});

View File

@ -0,0 +1,25 @@
// @strict: true
// @noEmit: true
// Repro from #52575
export interface Event<T> {
callback: (response: T) => void;
nested: {
nestedCallback: (response: T) => void;
}
}
export type CustomEvents = {
a: Event<string>
b: Event<number>
};
declare function emit<T extends keyof CustomEvents>(type: T, data: CustomEvents[T]): void
emit('a', {
callback: (r) => {},
nested: {
nestedCallback: (r) => {},
},
});