@@ -1881,6 +1881,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1881
1881
1882
1882
var tupleTypes = new Map<string, GenericType>();
1883
1883
var unionTypes = new Map<string, UnionType>();
1884
+ var unionOfUnionTypes = new Map<string, Type>();
1884
1885
var intersectionTypes = new Map<string, Type>();
1885
1886
var stringLiteralTypes = new Map<string, StringLiteralType>();
1886
1887
var numberLiteralTypes = new Map<number, NumberLiteralType>();
@@ -16228,9 +16229,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
16228
16229
16229
16230
function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) {
16230
16231
const flags = type.flags;
16231
- if (flags & TypeFlags.Union) {
16232
- return addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types);
16233
- }
16234
16232
// We ignore 'never' types in unions
16235
16233
if (!(flags & TypeFlags.Never)) {
16236
16234
includes |= flags & TypeFlags.IncludesMask;
@@ -16253,8 +16251,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
16253
16251
// Add the given types to the given type set. Order is preserved, duplicates are removed,
16254
16252
// and nested types of the given kind are flattened into the set.
16255
16253
function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: readonly Type[]): TypeFlags {
16254
+ let lastType: Type | undefined;
16256
16255
for (const type of types) {
16257
- includes = addTypeToUnion(typeSet, includes, type);
16256
+ // We skip the type if it is the same as the last type we processed. This simple test particularly
16257
+ // saves a lot of work for large lists of the same union type, such as when resolving `Record<A, B>[A]`,
16258
+ // where A and B are large union types.
16259
+ if (type !== lastType) {
16260
+ includes = type.flags & TypeFlags.Union ?
16261
+ addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types) :
16262
+ addTypeToUnion(typeSet, includes, type);
16263
+ lastType = type;
16264
+ }
16258
16265
}
16259
16266
return includes;
16260
16267
}
@@ -16406,6 +16413,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
16406
16413
if (types.length === 1) {
16407
16414
return types[0];
16408
16415
}
16416
+ // We optimize for the common case of unioning a union type with some other type (such as `undefined`).
16417
+ if (types.length === 2 && !origin && (types[0].flags & TypeFlags.Union || types[1].flags & TypeFlags.Union)) {
16418
+ const infix = unionReduction === UnionReduction.None ? "N" : unionReduction === UnionReduction.Subtype ? "S" : "L";
16419
+ const index = types[0].id < types[1].id ? 0 : 1;
16420
+ const id = types[index].id + infix + types[1 - index].id + getAliasId(aliasSymbol, aliasTypeArguments);
16421
+ let type = unionOfUnionTypes.get(id);
16422
+ if (!type) {
16423
+ type = getUnionTypeWorker(types, unionReduction, aliasSymbol, aliasTypeArguments, /*origin*/ undefined);
16424
+ unionOfUnionTypes.set(id, type);
16425
+ }
16426
+ return type;
16427
+ }
16428
+ return getUnionTypeWorker(types, unionReduction, aliasSymbol, aliasTypeArguments, origin);
16429
+ }
16430
+
16431
+ function getUnionTypeWorker(types: readonly Type[], unionReduction: UnionReduction, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, origin: Type | undefined): Type {
16409
16432
let typeSet: Type[] | undefined = [];
16410
16433
const includes = addTypesToUnion(typeSet, 0 as TypeFlags, types);
16411
16434
if (unionReduction !== UnionReduction.None) {
0 commit comments