@@ -13728,7 +13728,7 @@ namespace ts {
13728
13728
function getIntersectionType(types: readonly Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
13729
13729
const typeMembershipMap: ESMap<string, Type> = new Map();
13730
13730
const includes = addTypesToIntersection(typeMembershipMap, 0, types);
13731
- const typeSet: Type[] = arrayFrom(typeMembershipMap.values());
13731
+ let typeSet: Type[] = arrayFrom(typeMembershipMap.values());
13732
13732
// An intersection type is considered empty if it contains
13733
13733
// the type never, or
13734
13734
// more than one unit type or,
@@ -13789,6 +13789,41 @@ namespace ts {
13789
13789
result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
13790
13790
}
13791
13791
else {
13792
+ let runningResult: Type | undefined;
13793
+ const originalSet = typeSet;
13794
+ if (typeSet.length > 2 && getCrossProductUnionSize(typeSet) >= 100000 && every(typeSet, t => !!(t.flags & TypeFlags.Union) || !!(t.flags & TypeFlags.Primitive))) {
13795
+ // This type set is going to trigger an "expression too complex" error below. Rather than resort to that, as a last, best effort, when
13796
+ // the intersection looks like (A | B | C) & (D | E | F) & (G | H | I) - in the general case, this can result in a massive resulting
13797
+ // union, hence the check on the cross product size below, _however_ in some cases we can simplify the resulting type massively
13798
+ // - if we can recognize that upfront, we can still allow the type to form without creating innumerable intermediate types.
13799
+ // Specifically, in cases where almost all combinations are known to reduce to `never` (so the result is essentially sparse)
13800
+ // and we can recognize that quickly, we can use a simplified result without checking the worst-case size.
13801
+ // So we start with the assumption that the result _is_ sparse when the input looks like the above, and we assume the result
13802
+ // will take the form (A & D & G) | (B & E & H) | (C & F & I). To validate this, we reduce left, first combining
13803
+ // (A | B | C) & (D | E | F); if that combines into `(A & D) | (B & E) | (C & F)` like we want, which we make 9 intermediate
13804
+ // types to check, we can then combine the reduced `(A & D) | (B & E) | (C & F)` with (G | H | I), which again takes 9 intermediate types
13805
+ // to check, finally producing `(A & D & G) | (B & E & H) | (C & F & I)`. This required 18 intermediate types, while the standard method
13806
+ // of expanding (A | B | C) & (D | E | F) & (G | H | I) would produce 27 types and then perform reduction on the result.
13807
+ // By going elemnt-wise, and bailing if the result fails to reduce, we can allow these sparse expansions without doing undue work.
13808
+ runningResult = typeSet[0];
13809
+ for (let i = 1; i < typeSet.length; i++) {
13810
+ // for intersection reduction, here we're considering `undefined & (A | B)` as `never`. (ie, we're disallowing branded primitives)
13811
+ // This is relevant for, eg, when looking at `(HTMLElement | null) & (SVGElement | null) & ... & undefined` where _usually_
13812
+ // we'd allow for tons of garbage intermediate types like `null & SVGElement` to exist; but nobody ever really actually _wants_
13813
+ // that, IMO. Those types can still exist in the type system; just... not when working with unions and intersections with massive
13814
+ // cross-product growth potential.
13815
+ runningResult = typeSet[i].flags & TypeFlags.Primitive && everyType(runningResult, t => !!(t.flags & TypeFlags.Object)) ? neverType : getReducedType(intersectTypes(runningResult, typeSet[i]));
13816
+ if (i === typeSet.length - 1 || isTypeAny(runningResult) || runningResult.flags & TypeFlags.Never) {
13817
+ return runningResult;
13818
+ }
13819
+ if (!(runningResult.flags & TypeFlags.Union) || (runningResult as UnionType).types.length > typeSet.length) {
13820
+ // save work done by the accumulated result thus far, even if we're bailing on the heuristic
13821
+ // (it may have saved us enough work already that we're willing to work with the type now)
13822
+ typeSet = typeSet.slice(i + 1);
13823
+ break;
13824
+ }
13825
+ }
13826
+ }
13792
13827
// We are attempting to construct a type of the form X & (A | B) & (C | D). Transform this into a type of
13793
13828
// the form X & A & C | X & A & D | X & B & C | X & B & D. If the estimated size of the resulting union type
13794
13829
// exceeds 100000 constituents, report an error.
@@ -13798,8 +13833,8 @@ namespace ts {
13798
13833
const constituents = getCrossProductIntersections(typeSet);
13799
13834
// We attach a denormalized origin type when at least one constituent of the cross-product union is an
13800
13835
// intersection (i.e. when the intersection didn't just reduce one or more unions to smaller unions).
13801
- const origin = some(constituents, t => !!(t.flags & TypeFlags.Intersection)) ? createOriginUnionOrIntersectionType(TypeFlags.Intersection, typeSet ) : undefined;
13802
- result = getUnionType(constituents, UnionReduction.Literal, aliasSymbol, aliasTypeArguments, origin);
13836
+ const origin = some(constituents, t => !!(t.flags & TypeFlags.Intersection)) ? createOriginUnionOrIntersectionType(TypeFlags.Intersection, originalSet ) : undefined;
13837
+ result = runningResult ? getIntersectionType([runningResult, getUnionType(constituents, UnionReduction.Literal)], aliasSymbol, aliasTypeArguments) : getUnionType(constituents, UnionReduction.Literal, aliasSymbol, aliasTypeArguments, origin);
13803
13838
}
13804
13839
}
13805
13840
else {
0 commit comments