Skip to content

Commit 2047558

Browse files
committed
Optimize creation of union and intersection types
1 parent d0ab164 commit 2047558

1 file changed

Lines changed: 71 additions & 64 deletions

File tree

src/compiler/checker.ts

Lines changed: 71 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,21 @@ namespace ts {
580580
Both = Source | Target,
581581
}
582582

583+
const enum TypeIncludes {
584+
Any = 1 << 0,
585+
Undefined = 1 << 1,
586+
Null = 1 << 2,
587+
Never = 1 << 3,
588+
NonWideningType = 1 << 4,
589+
String = 1 << 5,
590+
Number = 1 << 6,
591+
ESSymbol = 1 << 7,
592+
LiteralOrUniqueESSymbol = 1 << 8,
593+
ObjectType = 1 << 9,
594+
EmptyObject = 1 << 10,
595+
Union = 1 << 11,
596+
}
597+
583598
const enum MembersOrExportsResolutionKind {
584599
resolvedExports = "resolvedExports",
585600
resolvedMembers = "resolvedMembers"
@@ -5516,6 +5531,8 @@ namespace ts {
55165531
sig.minArgumentCount = minArgumentCount;
55175532
sig.hasRestParameter = hasRestParameter;
55185533
sig.hasLiteralTypes = hasLiteralTypes;
5534+
sig.target = undefined;
5535+
sig.mapper = undefined;
55195536
return sig;
55205537
}
55215538

@@ -7419,21 +7436,6 @@ namespace ts {
74197436
return links.resolvedType;
74207437
}
74217438

7422-
interface TypeSet extends Array<Type> {
7423-
containsAny?: boolean;
7424-
containsUndefined?: boolean;
7425-
containsNull?: boolean;
7426-
containsNever?: boolean;
7427-
containsNonWideningType?: boolean;
7428-
containsString?: boolean;
7429-
containsNumber?: boolean;
7430-
containsESSymbol?: boolean;
7431-
containsLiteralOrUniqueESSymbol?: boolean;
7432-
containsObjectType?: boolean;
7433-
containsEmptyObject?: boolean;
7434-
unionIndex?: number;
7435-
}
7436-
74377439
function getTypeId(type: Type) {
74387440
return type.id;
74397441
}
@@ -7458,28 +7460,28 @@ namespace ts {
74587460
return false;
74597461
}
74607462

7461-
function addTypeToUnion(typeSet: TypeSet, type: Type) {
7463+
function addTypeToUnion(typeSet: Type[], includes: TypeIncludes, type: Type) {
74627464
const flags = type.flags;
74637465
if (flags & TypeFlags.Union) {
7464-
addTypesToUnion(typeSet, (<UnionType>type).types);
7466+
includes = addTypesToUnion(typeSet, includes, (<UnionType>type).types);
74657467
}
74667468
else if (flags & TypeFlags.Any) {
7467-
typeSet.containsAny = true;
7469+
includes |= TypeIncludes.Any;
74687470
}
74697471
else if (!strictNullChecks && flags & TypeFlags.Nullable) {
7470-
if (flags & TypeFlags.Undefined) typeSet.containsUndefined = true;
7471-
if (flags & TypeFlags.Null) typeSet.containsNull = true;
7472-
if (!(flags & TypeFlags.ContainsWideningType)) typeSet.containsNonWideningType = true;
7472+
if (flags & TypeFlags.Undefined) includes |= TypeIncludes.Undefined;
7473+
if (flags & TypeFlags.Null) includes |= TypeIncludes.Null;
7474+
if (!(flags & TypeFlags.ContainsWideningType)) includes |= TypeIncludes.NonWideningType;
74737475
}
74747476
else if (!(flags & TypeFlags.Never || flags & TypeFlags.Intersection && isEmptyIntersectionType(<IntersectionType>type))) {
74757477
// We ignore 'never' types in unions. Likewise, we ignore intersections of unit types as they are
74767478
// another form of 'never' (in that they have an empty value domain). We could in theory turn
74777479
// intersections of unit types into 'never' upon construction, but deferring the reduction makes it
74787480
// easier to reason about their origin.
7479-
if (flags & TypeFlags.String) typeSet.containsString = true;
7480-
if (flags & TypeFlags.Number) typeSet.containsNumber = true;
7481-
if (flags & TypeFlags.ESSymbol) typeSet.containsESSymbol = true;
7482-
if (flags & TypeFlags.StringOrNumberLiteralOrUnique) typeSet.containsLiteralOrUniqueESSymbol = true;
7481+
if (flags & TypeFlags.String) includes |= TypeIncludes.String;
7482+
if (flags & TypeFlags.Number) includes |= TypeIncludes.Number;
7483+
if (flags & TypeFlags.ESSymbol) includes |= TypeIncludes.ESSymbol;
7484+
if (flags & TypeFlags.StringOrNumberLiteralOrUnique) includes |= TypeIncludes.LiteralOrUniqueESSymbol;
74837485
const len = typeSet.length;
74847486
const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues);
74857487
if (index < 0) {
@@ -7489,14 +7491,16 @@ namespace ts {
74897491
}
74907492
}
74917493
}
7494+
return includes;
74927495
}
74937496

74947497
// Add the given types to the given type set. Order is preserved, duplicates are removed,
74957498
// and nested types of the given kind are flattened into the set.
7496-
function addTypesToUnion(typeSet: TypeSet, types: Type[]) {
7499+
function addTypesToUnion(typeSet: Type[], includes: TypeIncludes, types: Type[]): TypeIncludes {
74977500
for (const type of types) {
7498-
addTypeToUnion(typeSet, type);
7501+
includes = addTypeToUnion(typeSet, includes, type);
74997502
}
7503+
return includes;
75007504
}
75017505

75027506
function containsIdenticalType(types: Type[], type: Type) {
@@ -7520,7 +7524,7 @@ namespace ts {
75207524
return false;
75217525
}
75227526

7523-
function isSetOfLiteralsFromSameEnum(types: TypeSet): boolean {
7527+
function isSetOfLiteralsFromSameEnum(types: Type[]): boolean {
75247528
const first = types[0];
75257529
if (first.flags & TypeFlags.EnumLiteral) {
75267530
const firstEnum = getParentOfSymbol(first.symbol);
@@ -7536,7 +7540,7 @@ namespace ts {
75367540
return false;
75377541
}
75387542

7539-
function removeSubtypes(types: TypeSet) {
7543+
function removeSubtypes(types: Type[]) {
75407544
if (types.length === 0 || isSetOfLiteralsFromSameEnum(types)) {
75417545
return;
75427546
}
@@ -7549,15 +7553,15 @@ namespace ts {
75497553
}
75507554
}
75517555

7552-
function removeRedundantLiteralTypes(types: TypeSet) {
7556+
function removeRedundantLiteralTypes(types: Type[], includes: TypeIncludes) {
75537557
let i = types.length;
75547558
while (i > 0) {
75557559
i--;
75567560
const t = types[i];
75577561
const remove =
7558-
t.flags & TypeFlags.StringLiteral && types.containsString ||
7559-
t.flags & TypeFlags.NumberLiteral && types.containsNumber ||
7560-
t.flags & TypeFlags.UniqueESSymbol && types.containsESSymbol ||
7562+
t.flags & TypeFlags.StringLiteral && includes & TypeIncludes.String ||
7563+
t.flags & TypeFlags.NumberLiteral && includes & TypeIncludes.Number ||
7564+
t.flags & TypeFlags.UniqueESSymbol && includes & TypeIncludes.ESSymbol ||
75617565
t.flags & TypeFlags.StringOrNumberLiteral && t.flags & TypeFlags.FreshLiteral && containsType(types, (<LiteralType>t).regularType);
75627566
if (remove) {
75637567
orderedRemoveItemAt(types, i);
@@ -7579,24 +7583,24 @@ namespace ts {
75797583
if (types.length === 1) {
75807584
return types[0];
75817585
}
7582-
const typeSet = [] as TypeSet;
7583-
addTypesToUnion(typeSet, types);
7584-
if (typeSet.containsAny) {
7586+
const typeSet: Type[] = [];
7587+
const includes = addTypesToUnion(typeSet, 0, types);
7588+
if (includes & TypeIncludes.Any) {
75857589
return anyType;
75867590
}
75877591
switch (unionReduction) {
75887592
case UnionReduction.Literal:
7589-
if (typeSet.containsLiteralOrUniqueESSymbol) {
7590-
removeRedundantLiteralTypes(typeSet);
7593+
if (includes & TypeIncludes.LiteralOrUniqueESSymbol) {
7594+
removeRedundantLiteralTypes(typeSet, includes);
75917595
}
75927596
break;
75937597
case UnionReduction.Subtype:
75947598
removeSubtypes(typeSet);
75957599
break;
75967600
}
75977601
if (typeSet.length === 0) {
7598-
return typeSet.containsNull ? typeSet.containsNonWideningType ? nullType : nullWideningType :
7599-
typeSet.containsUndefined ? typeSet.containsNonWideningType ? undefinedType : undefinedWideningType :
7602+
return includes & TypeIncludes.Null ? includes & TypeIncludes.NonWideningType ? nullType : nullWideningType :
7603+
includes & TypeIncludes.Undefined ? includes & TypeIncludes.NonWideningType ? undefinedType : undefinedWideningType :
76007604
neverType;
76017605
}
76027606
return getUnionTypeFromSortedList(typeSet, aliasSymbol, aliasTypeArguments);
@@ -7674,39 +7678,42 @@ namespace ts {
76747678
return links.resolvedType;
76757679
}
76767680

7677-
function addTypeToIntersection(typeSet: TypeSet, type: Type) {
7678-
if (type.flags & TypeFlags.Intersection) {
7679-
addTypesToIntersection(typeSet, (<IntersectionType>type).types);
7681+
function addTypeToIntersection(typeSet: Type[], includes: TypeIncludes, type: Type) {
7682+
const flags = type.flags;
7683+
if (flags & TypeFlags.Intersection) {
7684+
includes = addTypesToIntersection(typeSet, includes, (<IntersectionType>type).types);
76807685
}
7681-
else if (type.flags & TypeFlags.Any) {
7682-
typeSet.containsAny = true;
7686+
else if (flags & TypeFlags.Any) {
7687+
includes |= TypeIncludes.Any;
76837688
}
7684-
else if (type.flags & TypeFlags.Never) {
7685-
typeSet.containsNever = true;
7689+
else if (flags & TypeFlags.Never) {
7690+
includes |= TypeIncludes.Never;
76867691
}
76877692
else if (getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type)) {
7688-
typeSet.containsEmptyObject = true;
7693+
includes |= TypeIncludes.EmptyObject;
76897694
}
7690-
else if ((strictNullChecks || !(type.flags & TypeFlags.Nullable)) && !contains(typeSet, type)) {
7691-
if (type.flags & TypeFlags.Object) {
7692-
typeSet.containsObjectType = true;
7695+
else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !contains(typeSet, type)) {
7696+
if (flags & TypeFlags.Object) {
7697+
includes |= TypeIncludes.ObjectType;
76937698
}
7694-
if (type.flags & TypeFlags.Union && typeSet.unionIndex === undefined) {
7695-
typeSet.unionIndex = typeSet.length;
7699+
if (flags & TypeFlags.Union) {
7700+
includes |= TypeIncludes.Union;
76967701
}
7697-
if (!(type.flags & TypeFlags.Object && (<ObjectType>type).objectFlags & ObjectFlags.Anonymous &&
7702+
if (!(flags & TypeFlags.Object && (<ObjectType>type).objectFlags & ObjectFlags.Anonymous &&
76987703
type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && containsIdenticalType(typeSet, type))) {
76997704
typeSet.push(type);
77007705
}
77017706
}
7707+
return includes;
77027708
}
77037709

77047710
// Add the given types to the given type set. Order is preserved, freshness is removed from literal
77057711
// types, duplicates are removed, and nested types of the given kind are flattened into the set.
7706-
function addTypesToIntersection(typeSet: TypeSet, types: Type[]) {
7712+
function addTypesToIntersection(typeSet: Type[], includes: TypeIncludes, types: Type[]) {
77077713
for (const type of types) {
7708-
addTypeToIntersection(typeSet, getRegularTypeOfLiteralType(type));
7714+
includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type));
77097715
}
7716+
return includes;
77107717
}
77117718

77127719
// We normalize combinations of intersection and union types based on the distributive property of the '&'
@@ -7723,27 +7730,27 @@ namespace ts {
77237730
if (types.length === 0) {
77247731
return emptyObjectType;
77257732
}
7726-
const typeSet = [] as TypeSet;
7727-
addTypesToIntersection(typeSet, types);
7728-
if (typeSet.containsNever) {
7733+
const typeSet: Type[] = [];
7734+
const includes = addTypesToIntersection(typeSet, 0, types);
7735+
if (includes & TypeIncludes.Never) {
77297736
return neverType;
77307737
}
7731-
if (typeSet.containsAny) {
7738+
if (includes & TypeIncludes.Any) {
77327739
return anyType;
77337740
}
7734-
if (typeSet.containsEmptyObject && !typeSet.containsObjectType) {
7741+
if (includes & TypeIncludes.EmptyObject && !(includes & TypeIncludes.ObjectType)) {
77357742
typeSet.push(emptyObjectType);
77367743
}
77377744
if (typeSet.length === 1) {
77387745
return typeSet[0];
77397746
}
7740-
const unionIndex = typeSet.unionIndex;
7741-
if (unionIndex !== undefined) {
7747+
if (includes & TypeIncludes.Union) {
77427748
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
77437749
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
7750+
const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0);
77447751
const unionType = <UnionType>typeSet[unionIndex];
77457752
return getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))),
7746-
UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
7753+
UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
77477754
}
77487755
const id = getTypeListId(typeSet);
77497756
let type = intersectionTypes.get(id);

0 commit comments

Comments
 (0)