diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 18c1f42f838fb..4a1089ff114e5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -391,7 +391,7 @@ namespace ts { const tupleTypes = createMap(); const unionTypes = createMap(); - const intersectionTypes = createMap(); + const intersectionTypes = createMap(); const literalTypes = createMap(); const indexedAccessTypes = createMap(); const substitutionTypes = createMap(); @@ -9838,6 +9838,15 @@ namespace ts { return true; } + function createIntersectionType(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: ReadonlyArray) { + const result = createType(TypeFlags.Intersection); + result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + result.types = types; + result.aliasSymbol = aliasSymbol; // See comment in `getUnionTypeFromSortedList`. + result.aliasTypeArguments = aliasTypeArguments; + return result; + } + // 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 @@ -9875,31 +9884,31 @@ namespace ts { if (typeSet.length === 1) { return typeSet[0]; } - if (includes & TypeFlags.Union) { - if (intersectUnionsOfPrimitiveTypes(typeSet)) { - // When the intersection creates a reduced set (which might mean that *all* union types have - // disappeared), we restart the operation to get a new set of combined flags. Once we have - // reduced we'll never reduce again, so this occurs at most once. - return getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); - } - // We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of - // the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain. - const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0); - const unionType = typeSet[unionIndex]; - return getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))), - UnionReduction.Literal, aliasSymbol, aliasTypeArguments); - } const id = getTypeListId(typeSet); - let type = intersectionTypes.get(id); - if (!type) { - type = createType(TypeFlags.Intersection); - intersectionTypes.set(id, type); - type.objectFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Nullable); - type.types = typeSet; - type.aliasSymbol = aliasSymbol; // See comment in `getUnionTypeFromSortedList`. - type.aliasTypeArguments = aliasTypeArguments; + let result = intersectionTypes.get(id); + if (!result) { + if (includes & TypeFlags.Union) { + if (intersectUnionsOfPrimitiveTypes(typeSet)) { + // When the intersection creates a reduced set (which might mean that *all* union types have + // disappeared), we restart the operation to get a new set of combined flags. Once we have + // reduced we'll never reduce again, so this occurs at most once. + result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); + } + else { + // We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of + // the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain. + const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0); + const unionType = typeSet[unionIndex]; + result = getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))), + UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + } + else { + result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); + } + intersectionTypes.set(id, result); } - return type; + return result; } function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type {