Skip to content

Commit b673d5f

Browse files
committed
Use binary searching in union types to improve performance
1 parent d7aa40d commit b673d5f

File tree

1 file changed

+88
-68
lines changed

1 file changed

+88
-68
lines changed

src/compiler/checker.ts

Lines changed: 88 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -118,18 +118,18 @@ namespace ts {
118118
const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__");
119119

120120
const anyType = createIntrinsicType(TypeFlags.Any, "any");
121+
const unknownType = createIntrinsicType(TypeFlags.Any, "unknown");
122+
const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined");
123+
const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsWideningType, "undefined");
124+
const nullType = createIntrinsicType(TypeFlags.Null, "null");
125+
const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsWideningType, "null");
121126
const stringType = createIntrinsicType(TypeFlags.String, "string");
122127
const numberType = createIntrinsicType(TypeFlags.Number, "number");
123128
const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true");
124129
const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false");
125130
const booleanType = createBooleanType([trueType, falseType]);
126131
const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol");
127132
const voidType = createIntrinsicType(TypeFlags.Void, "void");
128-
const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined");
129-
const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsWideningType, "undefined");
130-
const nullType = createIntrinsicType(TypeFlags.Null, "null");
131-
const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsWideningType, "null");
132-
const unknownType = createIntrinsicType(TypeFlags.Any, "unknown");
133133
const neverType = createIntrinsicType(TypeFlags.Never, "never");
134134

135135
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
@@ -1928,23 +1928,27 @@ namespace ts {
19281928
return result;
19291929
}
19301930

1931-
function reduceLiteralTypes(types: Type[]): Type[] {
1932-
let result: Type[];
1931+
function formatUnionTypes(types: Type[]): Type[] {
1932+
const result: Type[] = [];
1933+
let flags: TypeFlags = 0;
19331934
for (let i = 0; i < types.length; i++) {
19341935
const t = types[i];
1935-
if (t.flags & (TypeFlags.BooleanLiteral | TypeFlags.EnumLiteral)) {
1936-
const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : (<EnumLiteralType>t).baseType;
1937-
const count = baseType.types.length;
1938-
if (i + count <= types.length && types[i + count - 1] === baseType.types[count - 1]) {
1939-
(result || (result = types.slice(0, i))).push(baseType);
1940-
i += count - 1;
1941-
continue;
1936+
flags |= t.flags;
1937+
if (!(t.flags & TypeFlags.Nullable)) {
1938+
if (t.flags & (TypeFlags.BooleanLiteral | TypeFlags.EnumLiteral)) {
1939+
const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : (<EnumLiteralType>t).baseType;
1940+
const count = baseType.types.length;
1941+
if (i + count <= types.length && types[i + count - 1] === baseType.types[count - 1]) {
1942+
result.push(baseType);
1943+
i += count - 1;
1944+
continue;
1945+
}
19421946
}
1943-
}
1944-
if (result) {
19451947
result.push(t);
19461948
}
19471949
}
1950+
if (flags & TypeFlags.Null) result.push(nullType);
1951+
if (flags & TypeFlags.Undefined) result.push(undefinedType);
19481952
return result || types;
19491953
}
19501954

@@ -2246,7 +2250,7 @@ namespace ts {
22462250
writePunctuation(writer, SyntaxKind.OpenParenToken);
22472251
}
22482252
if (type.flags & TypeFlags.Union) {
2249-
writeTypeList(reduceLiteralTypes(type.types), SyntaxKind.BarToken);
2253+
writeTypeList(formatUnionTypes(type.types), SyntaxKind.BarToken);
22502254
}
22512255
else {
22522256
writeTypeList(type.types, SyntaxKind.AmpersandToken);
@@ -5228,27 +5232,58 @@ namespace ts {
52285232
containsNonWideningType?: boolean;
52295233
}
52305234

5231-
function addTypeToSet(typeSet: TypeSet, type: Type, typeSetKind: TypeFlags) {
5232-
if (type.flags & typeSetKind) {
5233-
addTypesToSet(typeSet, (<UnionOrIntersectionType>type).types, typeSetKind);
5235+
function binarySearchTypes(types: Type[], type: Type): number {
5236+
let low = 0;
5237+
let high = types.length - 1;
5238+
const typeId = type.id;
5239+
while (low <= high) {
5240+
const middle = low + ((high - low) >> 1);
5241+
const id = types[middle].id;
5242+
if (id === typeId) {
5243+
return middle;
5244+
}
5245+
else if (id > typeId) {
5246+
high = middle - 1;
5247+
}
5248+
else {
5249+
low = middle + 1;
5250+
}
5251+
}
5252+
return ~low;
5253+
}
5254+
5255+
function containsType(types: Type[], type: Type): boolean {
5256+
return binarySearchTypes(types, type) >= 0;
5257+
}
5258+
5259+
function addTypeToUnion(typeSet: TypeSet, type: Type) {
5260+
if (type.flags & TypeFlags.Union) {
5261+
addTypesToUnion(typeSet, (<UnionType>type).types);
52345262
}
5235-
else if (type.flags & (TypeFlags.Any | TypeFlags.Undefined | TypeFlags.Null)) {
5236-
if (type.flags & TypeFlags.Any) typeSet.containsAny = true;
5263+
else if (type.flags & TypeFlags.Any) {
5264+
typeSet.containsAny = true;
5265+
}
5266+
else if (!strictNullChecks && type.flags & TypeFlags.Nullable) {
52375267
if (type.flags & TypeFlags.Undefined) typeSet.containsUndefined = true;
52385268
if (type.flags & TypeFlags.Null) typeSet.containsNull = true;
52395269
if (!(type.flags & TypeFlags.ContainsWideningType)) typeSet.containsNonWideningType = true;
52405270
}
5241-
else if (type !== neverType && !contains(typeSet, type) &&
5242-
!(type.flags & TypeFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && containsIdenticalType(typeSet, type))) {
5243-
typeSet.push(type);
5271+
else if (!(type.flags & TypeFlags.Never)) {
5272+
const len = typeSet.length;
5273+
const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearchTypes(typeSet, type);
5274+
if (index < 0) {
5275+
if (!(type.flags & TypeFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && containsIdenticalType(typeSet, type))) {
5276+
typeSet.splice(~index, 0, type);
5277+
}
5278+
}
52445279
}
52455280
}
52465281

52475282
// Add the given types to the given type set. Order is preserved, duplicates are removed,
52485283
// and nested types of the given kind are flattened into the set.
5249-
function addTypesToSet(typeSet: TypeSet, types: Type[], typeSetKind: TypeFlags) {
5284+
function addTypesToUnion(typeSet: TypeSet, types: Type[]) {
52505285
for (const type of types) {
5251-
addTypeToSet(typeSet, type, typeSetKind);
5286+
addTypeToUnion(typeSet, type);
52525287
}
52535288
}
52545289

@@ -5280,10 +5315,6 @@ namespace ts {
52805315
}
52815316
}
52825317

5283-
function compareTypeIds(type1: Type, type2: Type): number {
5284-
return type1.id - type2.id;
5285-
}
5286-
52875318
// We deduplicate the constituent types based on object identity. If the subtypeReduction flag is
52885319
// specified we also reduce the constituent type set to only include types that aren't subtypes of
52895320
// other types. Subtype reduction is expensive for large union types and is possible only when union
@@ -5299,15 +5330,10 @@ namespace ts {
52995330
return types[0];
53005331
}
53015332
const typeSet = [] as TypeSet;
5302-
addTypesToSet(typeSet, types, TypeFlags.Union);
5333+
addTypesToUnion(typeSet, types);
53035334
if (typeSet.containsAny) {
53045335
return anyType;
53055336
}
5306-
typeSet.sort(compareTypeIds);
5307-
if (strictNullChecks) {
5308-
if (typeSet.containsNull) typeSet.push(nullType);
5309-
if (typeSet.containsUndefined) typeSet.push(undefinedType);
5310-
}
53115337
if (subtypeReduction) {
53125338
removeSubtypes(typeSet);
53135339
}
@@ -5339,6 +5365,26 @@ namespace ts {
53395365
return links.resolvedType;
53405366
}
53415367

5368+
function addTypeToIntersection(typeSet: TypeSet, type: Type) {
5369+
if (type.flags & TypeFlags.Intersection) {
5370+
addTypesToIntersection(typeSet, (<IntersectionType>type).types);
5371+
}
5372+
else if (type.flags & TypeFlags.Any) {
5373+
typeSet.containsAny = true;
5374+
}
5375+
else if (!(type.flags & TypeFlags.Never) && (strictNullChecks || !(type.flags & TypeFlags.Nullable)) && !contains(typeSet, type)) {
5376+
typeSet.push(type);
5377+
}
5378+
}
5379+
5380+
// Add the given types to the given type set. Order is preserved, duplicates are removed,
5381+
// and nested types of the given kind are flattened into the set.
5382+
function addTypesToIntersection(typeSet: TypeSet, types: Type[]) {
5383+
for (const type of types) {
5384+
addTypeToIntersection(typeSet, type);
5385+
}
5386+
}
5387+
53425388
// We do not perform structural deduplication on intersection types. Intersection types are created only by the &
53435389
// type operator and we can't reduce those because we want to support recursive intersection types. For example,
53445390
// a type alias of the form "type List<T> = T & { next: List<T> }" cannot be reduced during its declaration.
@@ -5349,14 +5395,10 @@ namespace ts {
53495395
return emptyObjectType;
53505396
}
53515397
const typeSet = [] as TypeSet;
5352-
addTypesToSet(typeSet, types, TypeFlags.Intersection);
5398+
addTypesToIntersection(typeSet, types);
53535399
if (typeSet.containsAny) {
53545400
return anyType;
53555401
}
5356-
if (strictNullChecks) {
5357-
if (typeSet.containsNull) typeSet.push(nullType);
5358-
if (typeSet.containsUndefined) typeSet.push(undefinedType);
5359-
}
53605402
if (typeSet.length === 1) {
53615403
return typeSet[0];
53625404
}
@@ -6395,21 +6437,10 @@ namespace ts {
63956437

63966438
function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary {
63976439
const targetTypes = target.types;
6398-
if (contains(targetTypes, source)) {
6440+
if (target.flags & TypeFlags.Union && containsType(targetTypes, source)) {
63996441
return Ternary.True;
64006442
}
6401-
// The null and undefined types are guaranteed to be at the end of the constituent type list. In order
6402-
// to produce the best possible errors we first check the nullable types, such that the last type we
6403-
// check and report errors from is a non-nullable type if one is present.
6404-
let len = targetTypes.length;
6405-
while (len >= 2 && targetTypes[len - 1].flags & TypeFlags.Nullable) {
6406-
const related = isRelatedTo(source, targetTypes[len - 1], /*reportErrors*/ false);
6407-
if (related) {
6408-
return related;
6409-
}
6410-
len--;
6411-
}
6412-
// Now check the non-nullable types and report errors on the last one.
6443+
const len = targetTypes.length;
64136444
for (let i = 0; i < len; i++) {
64146445
const related = isRelatedTo(source, targetTypes[i], reportErrors && i === len - 1);
64156446
if (related) {
@@ -6434,21 +6465,10 @@ namespace ts {
64346465

64356466
function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean): Ternary {
64366467
const sourceTypes = source.types;
6437-
if (contains(sourceTypes, target)) {
6468+
if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) {
64386469
return Ternary.True;
64396470
}
6440-
// The null and undefined types are guaranteed to be at the end of the constituent type list. In order
6441-
// to produce the best possible errors we first check the nullable types, such that the last type we
6442-
// check and report errors from is a non-nullable type if one is present.
6443-
let len = sourceTypes.length;
6444-
while (len >= 2 && sourceTypes[len - 1].flags & TypeFlags.Nullable) {
6445-
const related = isRelatedTo(sourceTypes[len - 1], target, /*reportErrors*/ false);
6446-
if (related) {
6447-
return related;
6448-
}
6449-
len--;
6450-
}
6451-
// Now check the non-nullable types and report errors on the last one.
6471+
const len = sourceTypes.length;
64526472
for (let i = 0; i < len; i++) {
64536473
const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1);
64546474
if (related) {

0 commit comments

Comments
 (0)