Skip to content

Commit 2f26088

Browse files
authored
Cache results of expensive repetitive type operations (#49760)
1 parent 501e442 commit 2f26088

File tree

1 file changed

+45
-23
lines changed

1 file changed

+45
-23
lines changed

src/compiler/checker.ts

+45-23
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,7 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
791791
const stringMappingTypes = new Map<string, StringMappingType>();
792792
const substitutionTypes = new Map<string, SubstitutionType>();
793793
const subtypeReductionCache = new Map<string, Type[]>();
794+
const cachedTypes = new Map<string, Type>();
794795
const evolvingArrayTypes: EvolvingArrayType[] = [];
795796
const undefinedProperties: SymbolTable = new Map();
796797
const markerTypes = new Set<number>();
@@ -1090,6 +1091,15 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
10901091

10911092
return checker;
10921093

1094+
function getCachedType(key: string | undefined) {
1095+
return key ? cachedTypes.get(key) : undefined;
1096+
}
1097+
1098+
function setCachedType(key: string | undefined, type: Type) {
1099+
if (key) cachedTypes.set(key, type);
1100+
return type;
1101+
}
1102+
10931103
function getJsxNamespace(location: Node | undefined): __String {
10941104
if (location) {
10951105
const file = getSourceFileOfNode(location);
@@ -21335,10 +21345,15 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
2133521345
type.flags & TypeFlags.NumberLiteral ? numberType :
2133621346
type.flags & TypeFlags.BigIntLiteral ? bigintType :
2133721347
type.flags & TypeFlags.BooleanLiteral ? booleanType :
21338-
type.flags & TypeFlags.Union ? mapType(type as UnionType, getBaseTypeOfLiteralType) :
21348+
type.flags & TypeFlags.Union ? getBaseTypeOfLiteralTypeUnion(type as UnionType) :
2133921349
type;
2134021350
}
2134121351

21352+
function getBaseTypeOfLiteralTypeUnion(type: UnionType) {
21353+
const key = `B${getTypeId(type)}`;
21354+
return getCachedType(key) ?? setCachedType(key, mapType(type, getBaseTypeOfLiteralType));
21355+
}
21356+
2134221357
function getWidenedLiteralType(type: Type): Type {
2134321358
return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(type as LiteralType) :
2134421359
type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType :
@@ -23574,23 +23589,25 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
2357423589
// For example, when a variable of type number | string | boolean is assigned a value of type number | boolean,
2357523590
// we remove type string.
2357623591
function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) {
23577-
if (declaredType !== assignedType) {
23578-
if (assignedType.flags & TypeFlags.Never) {
23579-
return assignedType;
23580-
}
23581-
let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
23582-
if (assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType)) {
23583-
reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types
23584-
}
23585-
// Our crude heuristic produces an invalid result in some cases: see GH#26130.
23586-
// For now, when that happens, we give up and don't narrow at all. (This also
23587-
// means we'll never narrow for erroneous assignments where the assigned type
23588-
// is not assignable to the declared type.)
23589-
if (isTypeAssignableTo(assignedType, reducedType)) {
23590-
return reducedType;
23591-
}
23592+
if (declaredType === assignedType) {
23593+
return declaredType;
2359223594
}
23593-
return declaredType;
23595+
if (assignedType.flags & TypeFlags.Never) {
23596+
return assignedType;
23597+
}
23598+
const key = `A${getTypeId(declaredType)},${getTypeId(assignedType)}`;
23599+
return getCachedType(key) ?? setCachedType(key, getAssignmentReducedTypeWorker(declaredType, assignedType));
23600+
}
23601+
23602+
function getAssignmentReducedTypeWorker(declaredType: UnionType, assignedType: Type) {
23603+
const filteredType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
23604+
// Ensure that we narrow to fresh types if the assignment is a fresh boolean literal type.
23605+
const reducedType = assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType) ? mapType(filteredType, getFreshTypeOfLiteralType) : filteredType;
23606+
// Our crude heuristic produces an invalid result in some cases: see GH#26130.
23607+
// For now, when that happens, we give up and don't narrow at all. (This also
23608+
// means we'll never narrow for erroneous assignments where the assigned type
23609+
// is not assignable to the declared type.)
23610+
return isTypeAssignableTo(assignedType, reducedType) ? reducedType : declaredType;
2359423611
}
2359523612

2359623613
function isFunctionObjectType(type: ObjectType): boolean {
@@ -25084,7 +25101,7 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
2508425101
const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration"))
2508525102
? getTypeOfSymbol(classSymbol) as InterfaceType
2508625103
: getDeclaredTypeOfSymbol(classSymbol);
25087-
return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom);
25104+
return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true);
2508825105
}
2508925106

2509025107
function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
@@ -25358,10 +25375,16 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
2535825375
if (!nonConstructorTypeInUnion) return type;
2535925376
}
2536025377

25361-
return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom);
25378+
return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true);
25379+
}
25380+
25381+
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) {
25382+
const key = type.flags & TypeFlags.Union ? `N${getTypeId(type)},${getTypeId(candidate)},${(assumeTrue ? 1 : 0) | (checkDerived ? 2 : 0)}` : undefined;
25383+
return getCachedType(key) ?? setCachedType(key, getNarrowedTypeWorker(type, candidate, assumeTrue, checkDerived));
2536225384
}
2536325385

25364-
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) {
25386+
function getNarrowedTypeWorker(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) {
25387+
const isRelated = checkDerived ? isTypeDerivedFrom : isTypeSubtypeOf;
2536525388
if (!assumeTrue) {
2536625389
return filterType(type, t => !isRelated(t, candidate));
2536725390
}
@@ -25373,7 +25396,6 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
2537325396
return assignableType;
2537425397
}
2537525398
}
25376-
2537725399
// If the candidate type is a subtype of the target type, narrow to the candidate type.
2537825400
// Otherwise, if the target type is assignable to the candidate type, keep the target type.
2537925401
// Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
@@ -25412,15 +25434,15 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
2541225434
const predicateArgument = getTypePredicateArgument(predicate, callExpression);
2541325435
if (predicateArgument) {
2541425436
if (isMatchingReference(reference, predicateArgument)) {
25415-
return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf);
25437+
return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ false);
2541625438
}
2541725439
if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) &&
2541825440
!(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) {
2541925441
type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
2542025442
}
2542125443
const access = getDiscriminantPropertyAccess(predicateArgument, type);
2542225444
if (access) {
25423-
return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, isTypeSubtypeOf));
25445+
return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, /*checkDerived*/ false));
2542425446
}
2542525447
}
2542625448
}

0 commit comments

Comments
 (0)