Skip to content

Commit 79d2772

Browse files
authored
Merge pull request microsoft#21573 from Microsoft/optimizeUnionIntersection
Optimize union and intersection types
2 parents 78f217b + 06c28ea commit 79d2772

2 files changed

Lines changed: 75 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"
@@ -5540,6 +5555,8 @@ namespace ts {
55405555
sig.minArgumentCount = minArgumentCount;
55415556
sig.hasRestParameter = hasRestParameter;
55425557
sig.hasLiteralTypes = hasLiteralTypes;
5558+
sig.target = undefined;
5559+
sig.mapper = undefined;
55435560
return sig;
55445561
}
55455562

@@ -7443,21 +7460,6 @@ namespace ts {
74437460
return links.resolvedType;
74447461
}
74457462

7446-
interface TypeSet extends Array<Type> {
7447-
containsAny?: boolean;
7448-
containsUndefined?: boolean;
7449-
containsNull?: boolean;
7450-
containsNever?: boolean;
7451-
containsNonWideningType?: boolean;
7452-
containsString?: boolean;
7453-
containsNumber?: boolean;
7454-
containsESSymbol?: boolean;
7455-
containsLiteralOrUniqueESSymbol?: boolean;
7456-
containsObjectType?: boolean;
7457-
containsEmptyObject?: boolean;
7458-
unionIndex?: number;
7459-
}
7460-
74617463
function getTypeId(type: Type) {
74627464
return type.id;
74637465
}
@@ -7482,28 +7484,28 @@ namespace ts {
74827484
return false;
74837485
}
74847486

7485-
function addTypeToUnion(typeSet: TypeSet, type: Type) {
7487+
function addTypeToUnion(typeSet: Type[], includes: TypeIncludes, type: Type) {
74867488
const flags = type.flags;
74877489
if (flags & TypeFlags.Union) {
7488-
addTypesToUnion(typeSet, (<UnionType>type).types);
7490+
includes = addTypesToUnion(typeSet, includes, (<UnionType>type).types);
74897491
}
74907492
else if (flags & TypeFlags.Any) {
7491-
typeSet.containsAny = true;
7493+
includes |= TypeIncludes.Any;
74927494
}
74937495
else if (!strictNullChecks && flags & TypeFlags.Nullable) {
7494-
if (flags & TypeFlags.Undefined) typeSet.containsUndefined = true;
7495-
if (flags & TypeFlags.Null) typeSet.containsNull = true;
7496-
if (!(flags & TypeFlags.ContainsWideningType)) typeSet.containsNonWideningType = true;
7496+
if (flags & TypeFlags.Undefined) includes |= TypeIncludes.Undefined;
7497+
if (flags & TypeFlags.Null) includes |= TypeIncludes.Null;
7498+
if (!(flags & TypeFlags.ContainsWideningType)) includes |= TypeIncludes.NonWideningType;
74977499
}
74987500
else if (!(flags & TypeFlags.Never || flags & TypeFlags.Intersection && isEmptyIntersectionType(<IntersectionType>type))) {
74997501
// We ignore 'never' types in unions. Likewise, we ignore intersections of unit types as they are
75007502
// another form of 'never' (in that they have an empty value domain). We could in theory turn
75017503
// intersections of unit types into 'never' upon construction, but deferring the reduction makes it
75027504
// easier to reason about their origin.
7503-
if (flags & TypeFlags.String) typeSet.containsString = true;
7504-
if (flags & TypeFlags.Number) typeSet.containsNumber = true;
7505-
if (flags & TypeFlags.ESSymbol) typeSet.containsESSymbol = true;
7506-
if (flags & TypeFlags.StringOrNumberLiteralOrUnique) typeSet.containsLiteralOrUniqueESSymbol = true;
7505+
if (flags & TypeFlags.String) includes |= TypeIncludes.String;
7506+
if (flags & TypeFlags.Number) includes |= TypeIncludes.Number;
7507+
if (flags & TypeFlags.ESSymbol) includes |= TypeIncludes.ESSymbol;
7508+
if (flags & TypeFlags.StringOrNumberLiteralOrUnique) includes |= TypeIncludes.LiteralOrUniqueESSymbol;
75077509
const len = typeSet.length;
75087510
const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues);
75097511
if (index < 0) {
@@ -7513,14 +7515,16 @@ namespace ts {
75137515
}
75147516
}
75157517
}
7518+
return includes;
75167519
}
75177520

75187521
// Add the given types to the given type set. Order is preserved, duplicates are removed,
75197522
// and nested types of the given kind are flattened into the set.
7520-
function addTypesToUnion(typeSet: TypeSet, types: Type[]) {
7523+
function addTypesToUnion(typeSet: Type[], includes: TypeIncludes, types: Type[]): TypeIncludes {
75217524
for (const type of types) {
7522-
addTypeToUnion(typeSet, type);
7525+
includes = addTypeToUnion(typeSet, includes, type);
75237526
}
7527+
return includes;
75247528
}
75257529

75267530
function containsIdenticalType(types: Type[], type: Type) {
@@ -7544,7 +7548,7 @@ namespace ts {
75447548
return false;
75457549
}
75467550

7547-
function isSetOfLiteralsFromSameEnum(types: TypeSet): boolean {
7551+
function isSetOfLiteralsFromSameEnum(types: Type[]): boolean {
75487552
const first = types[0];
75497553
if (first.flags & TypeFlags.EnumLiteral) {
75507554
const firstEnum = getParentOfSymbol(first.symbol);
@@ -7560,7 +7564,7 @@ namespace ts {
75607564
return false;
75617565
}
75627566

7563-
function removeSubtypes(types: TypeSet) {
7567+
function removeSubtypes(types: Type[]) {
75647568
if (types.length === 0 || isSetOfLiteralsFromSameEnum(types)) {
75657569
return;
75667570
}
@@ -7573,15 +7577,15 @@ namespace ts {
75737577
}
75747578
}
75757579

7576-
function removeRedundantLiteralTypes(types: TypeSet) {
7580+
function removeRedundantLiteralTypes(types: Type[], includes: TypeIncludes) {
75777581
let i = types.length;
75787582
while (i > 0) {
75797583
i--;
75807584
const t = types[i];
75817585
const remove =
7582-
t.flags & TypeFlags.StringLiteral && types.containsString ||
7583-
t.flags & TypeFlags.NumberLiteral && types.containsNumber ||
7584-
t.flags & TypeFlags.UniqueESSymbol && types.containsESSymbol ||
7586+
t.flags & TypeFlags.StringLiteral && includes & TypeIncludes.String ||
7587+
t.flags & TypeFlags.NumberLiteral && includes & TypeIncludes.Number ||
7588+
t.flags & TypeFlags.UniqueESSymbol && includes & TypeIncludes.ESSymbol ||
75857589
t.flags & TypeFlags.StringOrNumberLiteral && t.flags & TypeFlags.FreshLiteral && containsType(types, (<LiteralType>t).regularType);
75867590
if (remove) {
75877591
orderedRemoveItemAt(types, i);
@@ -7603,24 +7607,24 @@ namespace ts {
76037607
if (types.length === 1) {
76047608
return types[0];
76057609
}
7606-
const typeSet = [] as TypeSet;
7607-
addTypesToUnion(typeSet, types);
7608-
if (typeSet.containsAny) {
7610+
const typeSet: Type[] = [];
7611+
const includes = addTypesToUnion(typeSet, 0, types);
7612+
if (includes & TypeIncludes.Any) {
76097613
return anyType;
76107614
}
76117615
switch (unionReduction) {
76127616
case UnionReduction.Literal:
7613-
if (typeSet.containsLiteralOrUniqueESSymbol) {
7614-
removeRedundantLiteralTypes(typeSet);
7617+
if (includes & TypeIncludes.LiteralOrUniqueESSymbol) {
7618+
removeRedundantLiteralTypes(typeSet, includes);
76157619
}
76167620
break;
76177621
case UnionReduction.Subtype:
76187622
removeSubtypes(typeSet);
76197623
break;
76207624
}
76217625
if (typeSet.length === 0) {
7622-
return typeSet.containsNull ? typeSet.containsNonWideningType ? nullType : nullWideningType :
7623-
typeSet.containsUndefined ? typeSet.containsNonWideningType ? undefinedType : undefinedWideningType :
7626+
return includes & TypeIncludes.Null ? includes & TypeIncludes.NonWideningType ? nullType : nullWideningType :
7627+
includes & TypeIncludes.Undefined ? includes & TypeIncludes.NonWideningType ? undefinedType : undefinedWideningType :
76247628
neverType;
76257629
}
76267630
return getUnionTypeFromSortedList(typeSet, aliasSymbol, aliasTypeArguments);
@@ -7698,39 +7702,42 @@ namespace ts {
76987702
return links.resolvedType;
76997703
}
77007704

7701-
function addTypeToIntersection(typeSet: TypeSet, type: Type) {
7702-
if (type.flags & TypeFlags.Intersection) {
7703-
addTypesToIntersection(typeSet, (<IntersectionType>type).types);
7705+
function addTypeToIntersection(typeSet: Type[], includes: TypeIncludes, type: Type) {
7706+
const flags = type.flags;
7707+
if (flags & TypeFlags.Intersection) {
7708+
includes = addTypesToIntersection(typeSet, includes, (<IntersectionType>type).types);
77047709
}
7705-
else if (type.flags & TypeFlags.Any) {
7706-
typeSet.containsAny = true;
7710+
else if (flags & TypeFlags.Any) {
7711+
includes |= TypeIncludes.Any;
77077712
}
7708-
else if (type.flags & TypeFlags.Never) {
7709-
typeSet.containsNever = true;
7713+
else if (flags & TypeFlags.Never) {
7714+
includes |= TypeIncludes.Never;
77107715
}
77117716
else if (getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type)) {
7712-
typeSet.containsEmptyObject = true;
7717+
includes |= TypeIncludes.EmptyObject;
77137718
}
7714-
else if ((strictNullChecks || !(type.flags & TypeFlags.Nullable)) && !contains(typeSet, type)) {
7715-
if (type.flags & TypeFlags.Object) {
7716-
typeSet.containsObjectType = true;
7719+
else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !contains(typeSet, type)) {
7720+
if (flags & TypeFlags.Object) {
7721+
includes |= TypeIncludes.ObjectType;
77177722
}
7718-
if (type.flags & TypeFlags.Union && typeSet.unionIndex === undefined) {
7719-
typeSet.unionIndex = typeSet.length;
7723+
if (flags & TypeFlags.Union) {
7724+
includes |= TypeIncludes.Union;
77207725
}
7721-
if (!(type.flags & TypeFlags.Object && (<ObjectType>type).objectFlags & ObjectFlags.Anonymous &&
7726+
if (!(flags & TypeFlags.Object && (<ObjectType>type).objectFlags & ObjectFlags.Anonymous &&
77227727
type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && containsIdenticalType(typeSet, type))) {
77237728
typeSet.push(type);
77247729
}
77257730
}
7731+
return includes;
77267732
}
77277733

77287734
// Add the given types to the given type set. Order is preserved, freshness is removed from literal
77297735
// types, duplicates are removed, and nested types of the given kind are flattened into the set.
7730-
function addTypesToIntersection(typeSet: TypeSet, types: Type[]) {
7736+
function addTypesToIntersection(typeSet: Type[], includes: TypeIncludes, types: Type[]) {
77317737
for (const type of types) {
7732-
addTypeToIntersection(typeSet, getRegularTypeOfLiteralType(type));
7738+
includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type));
77337739
}
7740+
return includes;
77347741
}
77357742

77367743
// We normalize combinations of intersection and union types based on the distributive property of the '&'
@@ -7747,27 +7754,27 @@ namespace ts {
77477754
if (types.length === 0) {
77487755
return emptyObjectType;
77497756
}
7750-
const typeSet = [] as TypeSet;
7751-
addTypesToIntersection(typeSet, types);
7752-
if (typeSet.containsNever) {
7757+
const typeSet: Type[] = [];
7758+
const includes = addTypesToIntersection(typeSet, 0, types);
7759+
if (includes & TypeIncludes.Never) {
77537760
return neverType;
77547761
}
7755-
if (typeSet.containsAny) {
7762+
if (includes & TypeIncludes.Any) {
77567763
return anyType;
77577764
}
7758-
if (typeSet.containsEmptyObject && !typeSet.containsObjectType) {
7765+
if (includes & TypeIncludes.EmptyObject && !(includes & TypeIncludes.ObjectType)) {
77597766
typeSet.push(emptyObjectType);
77607767
}
77617768
if (typeSet.length === 1) {
77627769
return typeSet[0];
77637770
}
7764-
const unionIndex = typeSet.unionIndex;
7765-
if (unionIndex !== undefined) {
7771+
if (includes & TypeIncludes.Union) {
77667772
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
77677773
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
7774+
const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0);
77687775
const unionType = <UnionType>typeSet[unionIndex];
77697776
return getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))),
7770-
UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
7777+
UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
77717778
}
77727779
const id = getTypeListId(typeSet);
77737780
let type = intersectionTypes.get(id);

src/compiler/core.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2778,6 +2778,10 @@ namespace ts {
27782778
this.flags = flags;
27792779
this.escapedName = name;
27802780
this.declarations = undefined;
2781+
this.valueDeclaration = undefined;
2782+
this.id = undefined;
2783+
this.mergeId = undefined;
2784+
this.parent = undefined;
27812785
}
27822786

27832787
function Type(this: Type, checker: TypeChecker, flags: TypeFlags) {

0 commit comments

Comments
 (0)