@@ -1881,6 +1881,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
18811881
18821882 var tupleTypes = new Map<string, GenericType>();
18831883 var unionTypes = new Map<string, UnionType>();
1884+ var unionOfUnionTypes = new Map<string, Type>();
18841885 var intersectionTypes = new Map<string, Type>();
18851886 var stringLiteralTypes = new Map<string, StringLiteralType>();
18861887 var numberLiteralTypes = new Map<number, NumberLiteralType>();
@@ -16228,9 +16229,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1622816229
1622916230 function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) {
1623016231 const flags = type.flags;
16231- if (flags & TypeFlags.Union) {
16232- return addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types);
16233- }
1623416232 // We ignore 'never' types in unions
1623516233 if (!(flags & TypeFlags.Never)) {
1623616234 includes |= flags & TypeFlags.IncludesMask;
@@ -16253,8 +16251,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1625316251 // Add the given types to the given type set. Order is preserved, duplicates are removed,
1625416252 // and nested types of the given kind are flattened into the set.
1625516253 function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: readonly Type[]): TypeFlags {
16254+ let lastType: Type | undefined;
1625616255 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+ }
1625816265 }
1625916266 return includes;
1626016267 }
@@ -16406,6 +16413,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1640616413 if (types.length === 1) {
1640716414 return types[0];
1640816415 }
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 {
1640916432 let typeSet: Type[] | undefined = [];
1641016433 const includes = addTypesToUnion(typeSet, 0 as TypeFlags, types);
1641116434 if (unionReduction !== UnionReduction.None) {
0 commit comments