Skip to content

Commit cb09077

Browse files
committed
Cache results of expensive repetitive type operations
1 parent c251d60 commit cb09077

File tree

1 file changed

+45
-23
lines changed

1 file changed

+45
-23
lines changed

src/compiler/checker.ts

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,7 @@ namespace ts {
774774
const stringMappingTypes = new Map<string, StringMappingType>();
775775
const substitutionTypes = new Map<string, SubstitutionType>();
776776
const subtypeReductionCache = new Map<string, Type[]>();
777+
const cachedTypes = new Map<string, Type>();
777778
const evolvingArrayTypes: EvolvingArrayType[] = [];
778779
const undefinedProperties: SymbolTable = new Map();
779780
const markerTypes = new Set<number>();
@@ -1061,6 +1062,15 @@ namespace ts {
10611062

10621063
return checker;
10631064

1065+
function getCachedType(key: string | undefined) {
1066+
return key ? cachedTypes.get(key) : undefined;
1067+
}
1068+
1069+
function setCachedType(key: string | undefined, type: Type) {
1070+
if (key) cachedTypes.set(key, type);
1071+
return type;
1072+
}
1073+
10641074
function getJsxNamespace(location: Node | undefined): __String {
10651075
if (location) {
10661076
const file = getSourceFileOfNode(location);
@@ -21295,10 +21305,15 @@ namespace ts {
2129521305
type.flags & TypeFlags.NumberLiteral ? numberType :
2129621306
type.flags & TypeFlags.BigIntLiteral ? bigintType :
2129721307
type.flags & TypeFlags.BooleanLiteral ? booleanType :
21298-
type.flags & TypeFlags.Union ? mapType(type as UnionType, getBaseTypeOfLiteralType) :
21308+
type.flags & TypeFlags.Union ? getBaseTypeOfLiteralTypeUnion(type as UnionType) :
2129921309
type;
2130021310
}
2130121311

21312+
function getBaseTypeOfLiteralTypeUnion(type: UnionType) {
21313+
const key = `B${getTypeId(type)}`;
21314+
return getCachedType(key) ?? setCachedType(key, mapType(type, getBaseTypeOfLiteralType));
21315+
}
21316+
2130221317
function getWidenedLiteralType(type: Type): Type {
2130321318
return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(type as LiteralType) :
2130421319
type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType :
@@ -23531,23 +23546,25 @@ namespace ts {
2353123546
// For example, when a variable of type number | string | boolean is assigned a value of type number | boolean,
2353223547
// we remove type string.
2353323548
function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) {
23534-
if (declaredType !== assignedType) {
23535-
if (assignedType.flags & TypeFlags.Never) {
23536-
return assignedType;
23537-
}
23538-
let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
23539-
if (assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType)) {
23540-
reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types
23541-
}
23542-
// Our crude heuristic produces an invalid result in some cases: see GH#26130.
23543-
// For now, when that happens, we give up and don't narrow at all. (This also
23544-
// means we'll never narrow for erroneous assignments where the assigned type
23545-
// is not assignable to the declared type.)
23546-
if (isTypeAssignableTo(assignedType, reducedType)) {
23547-
return reducedType;
23548-
}
23549+
if (declaredType === assignedType) {
23550+
return declaredType;
2354923551
}
23550-
return declaredType;
23552+
if (assignedType.flags & TypeFlags.Never) {
23553+
return assignedType;
23554+
}
23555+
const key = `A${getTypeId(declaredType)},${getTypeId(assignedType)}`;
23556+
return getCachedType(key) ?? setCachedType(key, getAssignmentReducedTypeWorker(declaredType, assignedType));
23557+
}
23558+
23559+
function getAssignmentReducedTypeWorker(declaredType: UnionType, assignedType: Type) {
23560+
const filteredType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
23561+
// Ensure that we narrow to fresh types if the assignment is a fresh boolean literal type.
23562+
const reducedType = assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType) ? mapType(filteredType, getFreshTypeOfLiteralType) : filteredType;
23563+
// Our crude heuristic produces an invalid result in some cases: see GH#26130.
23564+
// For now, when that happens, we give up and don't narrow at all. (This also
23565+
// means we'll never narrow for erroneous assignments where the assigned type
23566+
// is not assignable to the declared type.)
23567+
return isTypeAssignableTo(assignedType, reducedType) ? reducedType : declaredType;
2355123568
}
2355223569

2355323570
function isFunctionObjectType(type: ObjectType): boolean {
@@ -25041,7 +25058,7 @@ namespace ts {
2504125058
const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration"))
2504225059
? getTypeOfSymbol(classSymbol) as InterfaceType
2504325060
: getDeclaredTypeOfSymbol(classSymbol);
25044-
return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom);
25061+
return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true);
2504525062
}
2504625063

2504725064
function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
@@ -25315,10 +25332,16 @@ namespace ts {
2531525332
if (!nonConstructorTypeInUnion) return type;
2531625333
}
2531725334

25318-
return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom);
25335+
return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true);
25336+
}
25337+
25338+
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) {
25339+
const key = type.flags & TypeFlags.Union ? `N${getTypeId(type)},${getTypeId(candidate)},${(assumeTrue ? 1 : 0) | (checkDerived ? 2 : 0)}` : undefined;
25340+
return getCachedType(key) ?? setCachedType(key, getNarrowedTypeWorker(type, candidate, assumeTrue, checkDerived));
2531925341
}
2532025342

25321-
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) {
25343+
function getNarrowedTypeWorker(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) {
25344+
const isRelated = checkDerived ? isTypeDerivedFrom : isTypeSubtypeOf;
2532225345
if (!assumeTrue) {
2532325346
return filterType(type, t => !isRelated(t, candidate));
2532425347
}
@@ -25330,7 +25353,6 @@ namespace ts {
2533025353
return assignableType;
2533125354
}
2533225355
}
25333-
2533425356
// If the candidate type is a subtype of the target type, narrow to the candidate type.
2533525357
// Otherwise, if the target type is assignable to the candidate type, keep the target type.
2533625358
// Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
@@ -25369,15 +25391,15 @@ namespace ts {
2536925391
const predicateArgument = getTypePredicateArgument(predicate, callExpression);
2537025392
if (predicateArgument) {
2537125393
if (isMatchingReference(reference, predicateArgument)) {
25372-
return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf);
25394+
return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ false);
2537325395
}
2537425396
if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) &&
2537525397
!(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) {
2537625398
type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
2537725399
}
2537825400
const access = getDiscriminantPropertyAccess(predicateArgument, type);
2537925401
if (access) {
25380-
return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, isTypeSubtypeOf));
25402+
return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, /*checkDerived*/ false));
2538125403
}
2538225404
}
2538325405
}

0 commit comments

Comments
 (0)