Optimize union type creation (#53771)

This commit is contained in:
Anders Hejlsberg 2023-04-14 09:34:12 -07:00 committed by GitHub
parent 3445e58815
commit b798e6bfa5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1881,6 +1881,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
var tupleTypes = new Map<string, GenericType>();
var unionTypes = new Map<string, UnionType>();
var unionOfUnionTypes = new Map<string, Type>();
var intersectionTypes = new Map<string, Type>();
var stringLiteralTypes = new Map<string, StringLiteralType>();
var numberLiteralTypes = new Map<number, NumberLiteralType>();
@ -16228,9 +16229,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) {
const flags = type.flags;
if (flags & TypeFlags.Union) {
return addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types);
}
// We ignore 'never' types in unions
if (!(flags & TypeFlags.Never)) {
includes |= flags & TypeFlags.IncludesMask;
@ -16253,8 +16251,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Add the given types to the given type set. Order is preserved, duplicates are removed,
// and nested types of the given kind are flattened into the set.
function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: readonly Type[]): TypeFlags {
let lastType: Type | undefined;
for (const type of types) {
includes = addTypeToUnion(typeSet, includes, type);
// We skip the type if it is the same as the last type we processed. This simple test particularly
// saves a lot of work for large lists of the same union type, such as when resolving `Record<A, B>[A]`,
// where A and B are large union types.
if (type !== lastType) {
includes = type.flags & TypeFlags.Union ?
addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types) :
addTypeToUnion(typeSet, includes, type);
lastType = type;
}
}
return includes;
}
@ -16406,6 +16413,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (types.length === 1) {
return types[0];
}
// We optimize for the common case of unioning a union type with some other type (such as `undefined`).
if (types.length === 2 && !origin && (types[0].flags & TypeFlags.Union || types[1].flags & TypeFlags.Union)) {
const infix = unionReduction === UnionReduction.None ? "N" : unionReduction === UnionReduction.Subtype ? "S" : "L";
const index = types[0].id < types[1].id ? 0 : 1;
const id = types[index].id + infix + types[1 - index].id + getAliasId(aliasSymbol, aliasTypeArguments);
let type = unionOfUnionTypes.get(id);
if (!type) {
type = getUnionTypeWorker(types, unionReduction, aliasSymbol, aliasTypeArguments, /*origin*/ undefined);
unionOfUnionTypes.set(id, type);
}
return type;
}
return getUnionTypeWorker(types, unionReduction, aliasSymbol, aliasTypeArguments, origin);
}
function getUnionTypeWorker(types: readonly Type[], unionReduction: UnionReduction, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, origin: Type | undefined): Type {
let typeSet: Type[] | undefined = [];
const includes = addTypesToUnion(typeSet, 0 as TypeFlags, types);
if (unionReduction !== UnionReduction.None) {