@@ -774,6 +774,7 @@ namespace ts {
774
774
const stringMappingTypes = new Map<string, StringMappingType>();
775
775
const substitutionTypes = new Map<string, SubstitutionType>();
776
776
const subtypeReductionCache = new Map<string, Type[]>();
777
+ const cachedTypes = new Map<string, Type>();
777
778
const evolvingArrayTypes: EvolvingArrayType[] = [];
778
779
const undefinedProperties: SymbolTable = new Map();
779
780
const markerTypes = new Set<number>();
@@ -1061,6 +1062,15 @@ namespace ts {
1061
1062
1062
1063
return checker;
1063
1064
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
+
1064
1074
function getJsxNamespace(location: Node | undefined): __String {
1065
1075
if (location) {
1066
1076
const file = getSourceFileOfNode(location);
@@ -21295,10 +21305,15 @@ namespace ts {
21295
21305
type.flags & TypeFlags.NumberLiteral ? numberType :
21296
21306
type.flags & TypeFlags.BigIntLiteral ? bigintType :
21297
21307
type.flags & TypeFlags.BooleanLiteral ? booleanType :
21298
- type.flags & TypeFlags.Union ? mapType (type as UnionType, getBaseTypeOfLiteralType ) :
21308
+ type.flags & TypeFlags.Union ? getBaseTypeOfLiteralTypeUnion (type as UnionType) :
21299
21309
type;
21300
21310
}
21301
21311
21312
+ function getBaseTypeOfLiteralTypeUnion(type: UnionType) {
21313
+ const key = `B${getTypeId(type)}`;
21314
+ return getCachedType(key) ?? setCachedType(key, mapType(type, getBaseTypeOfLiteralType));
21315
+ }
21316
+
21302
21317
function getWidenedLiteralType(type: Type): Type {
21303
21318
return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(type as LiteralType) :
21304
21319
type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType :
@@ -23531,23 +23546,25 @@ namespace ts {
23531
23546
// For example, when a variable of type number | string | boolean is assigned a value of type number | boolean,
23532
23547
// we remove type string.
23533
23548
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;
23549
23551
}
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;
23551
23568
}
23552
23569
23553
23570
function isFunctionObjectType(type: ObjectType): boolean {
@@ -25041,7 +25058,7 @@ namespace ts {
25041
25058
const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration"))
25042
25059
? getTypeOfSymbol(classSymbol) as InterfaceType
25043
25060
: getDeclaredTypeOfSymbol(classSymbol);
25044
- return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom );
25061
+ return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true );
25045
25062
}
25046
25063
25047
25064
function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
@@ -25315,10 +25332,16 @@ namespace ts {
25315
25332
if (!nonConstructorTypeInUnion) return type;
25316
25333
}
25317
25334
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));
25319
25341
}
25320
25342
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;
25322
25345
if (!assumeTrue) {
25323
25346
return filterType(type, t => !isRelated(t, candidate));
25324
25347
}
@@ -25330,7 +25353,6 @@ namespace ts {
25330
25353
return assignableType;
25331
25354
}
25332
25355
}
25333
-
25334
25356
// If the candidate type is a subtype of the target type, narrow to the candidate type.
25335
25357
// Otherwise, if the target type is assignable to the candidate type, keep the target type.
25336
25358
// Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
@@ -25369,15 +25391,15 @@ namespace ts {
25369
25391
const predicateArgument = getTypePredicateArgument(predicate, callExpression);
25370
25392
if (predicateArgument) {
25371
25393
if (isMatchingReference(reference, predicateArgument)) {
25372
- return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf );
25394
+ return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ false );
25373
25395
}
25374
25396
if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) &&
25375
25397
!(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) {
25376
25398
type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
25377
25399
}
25378
25400
const access = getDiscriminantPropertyAccess(predicateArgument, type);
25379
25401
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 ));
25381
25403
}
25382
25404
}
25383
25405
}
0 commit comments