Skip to content

Commit b798e6b

Browse files
authored
Optimize union type creation (#53771)
1 parent 3445e58 commit b798e6b

File tree

1 file changed

+27
-4
lines changed

1 file changed

+27
-4
lines changed

src/compiler/checker.ts

+27-4
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)