From 426a63e8b6a98b2f6ff7eeddf98f0653582c744f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 15 May 2018 12:24:40 -0700 Subject: [PATCH] Optimize intersections of unions of unit types --- src/compiler/checker.ts | 32 ++++++++++++++++++++++++++++---- src/compiler/types.ts | 4 ++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index caa75a45eb9..705ecea6b5e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8319,7 +8319,7 @@ namespace ts { includes & TypeFlags.Undefined ? includes & TypeFlags.NonWideningType ? undefinedType : undefinedWideningType : neverType; } - return getUnionTypeFromSortedList(typeSet, aliasSymbol, aliasTypeArguments); + return getUnionTypeFromSortedList(typeSet, includes & TypeFlags.NotUnit ? 0 : TypeFlags.UnionOfUnitTypes, aliasSymbol, aliasTypeArguments); } function getUnionTypePredicate(signatures: ReadonlyArray): TypePredicate { @@ -8359,7 +8359,7 @@ namespace ts { } // This function assumes the constituent type list is sorted and deduplicated. - function getUnionTypeFromSortedList(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { + function getUnionTypeFromSortedList(types: Type[], unionOfUnitTypes: TypeFlags, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { if (types.length === 0) { return neverType; } @@ -8370,7 +8370,7 @@ namespace ts { let type = unionTypes.get(id); if (!type) { const propagatedFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); - type = createType(TypeFlags.Union | propagatedFlags); + type = createType(TypeFlags.Union | propagatedFlags | unionOfUnitTypes); unionTypes.set(id, type); type.types = types; /* @@ -8441,6 +8441,27 @@ namespace ts { } } + // When intersecting unions of unit types we can simply intersect based on type identity. + // Here we remove all unions of unit types from the given list and replace them with a + // a single union containing an intersection of the unit types. + function intersectUnionsOfUnitTypes(types: Type[]) { + const unionIndex = findIndex(types, t => (t.flags & TypeFlags.UnionOfUnitTypes) !== 0); + const unionType = types[unionIndex]; + let intersection = unionType.types; + let i = types.length - 1; + while (i > unionIndex) { + const t = types[i]; + if (t.flags & TypeFlags.UnionOfUnitTypes) { + intersection = filter(intersection, u => containsType((t).types, u)); + orderedRemoveItemAt(types, i); + } + i--; + } + if (intersection !== unionType.types) { + types[unionIndex] = getUnionTypeFromSortedList(intersection, unionType.flags & TypeFlags.UnionOfUnitTypes); + } + } + // We normalize combinations of intersection and union types based on the distributive property of the '&' // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection // types with union type constituents into equivalent union types with intersection type constituents and @@ -8468,6 +8489,9 @@ namespace ts { includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol) { removeRedundantPrimitiveTypes(typeSet, includes); } + if (includes & TypeFlags.UnionOfUnitTypes) { + intersectUnionsOfUnitTypes(typeSet); + } if (includes & TypeFlags.EmptyObject && !(includes & TypeFlags.Object)) { typeSet.push(emptyObjectType); } @@ -13234,7 +13258,7 @@ namespace ts { if (type.flags & TypeFlags.Union) { const types = (type).types; const filtered = filter(types, f); - return filtered === types ? type : getUnionTypeFromSortedList(filtered); + return filtered === types ? type : getUnionTypeFromSortedList(filtered, type.flags & TypeFlags.UnionOfUnitTypes); } return f(type) ? type : neverType; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4b2a7c080d2..84844c437aa 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3674,6 +3674,8 @@ namespace ts { ContainsAnyFunctionType = 1 << 26, // Type is or contains the anyFunctionType NonPrimitive = 1 << 27, // intrinsic object type /* @internal */ + UnionOfUnitTypes = 1 << 28, // Type is union of unit types + /* @internal */ GenericMappedType = 1 << 29, // Flag used by maybeTypeOfKind /* @internal */ @@ -3711,6 +3713,8 @@ namespace ts { Narrowable = Any | StructuredOrInstantiable | StringLike | NumberLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive, NotUnionOrUnit = Any | ESSymbol | Object | NonPrimitive, /* @internal */ + NotUnit = Any | String | Number | Boolean | Enum | ESSymbol | Void | Never | StructuredOrInstantiable, + /* @internal */ RequiresWidening = ContainsWideningType | ContainsObjectLiteral, /* @internal */ PropagatingFlags = ContainsWideningType | ContainsObjectLiteral | ContainsAnyFunctionType,