@@ -726,6 +726,7 @@ namespace ts {
726
726
const templateLiteralTypes = new Map<string, TemplateLiteralType>();
727
727
const stringMappingTypes = new Map<string, StringMappingType>();
728
728
const substitutionTypes = new Map<string, SubstitutionType>();
729
+ const subtypeReductionCache = new Map<string, Type[]>();
729
730
const evolvingArrayTypes: EvolvingArrayType[] = [];
730
731
const undefinedProperties: SymbolTable = new Map();
731
732
@@ -13320,19 +13321,51 @@ namespace ts {
13320
13321
return includes;
13321
13322
}
13322
13323
13323
- function removeSubtypes(types: Type[], hasObjectTypes: boolean): boolean {
13324
+ function removeSubtypes(types: Type[], hasObjectTypes: boolean): Type[] | undefined {
13325
+ const id = getTypeListId(types);
13326
+ const match = subtypeReductionCache.get(id);
13327
+ if (match) {
13328
+ return match;
13329
+ }
13324
13330
// We assume that redundant primitive types have already been removed from the types array and that there
13325
13331
// are no any and unknown types in the array. Thus, the only possible supertypes for primitive types are empty
13326
13332
// object types, and if none of those are present we can exclude primitive types from the subtype check.
13327
13333
const hasEmptyObject = hasObjectTypes && some(types, t => !!(t.flags & TypeFlags.Object) && !isGenericMappedType(t) && isEmptyResolvedType(resolveStructuredTypeMembers(<ObjectType>t)));
13328
13334
const len = types.length;
13329
13335
let i = len;
13336
+ let count = 0;
13330
13337
while (i > 0) {
13331
13338
i--;
13332
13339
const source = types[i];
13333
13340
if (hasEmptyObject || source.flags & TypeFlags.StructuredOrInstantiable) {
13341
+ // Find the first property with a unit type, if any. When constituents have a property by the same name
13342
+ // but of a different unit type, we can quickly disqualify them from subtype checks. This helps subtype
13343
+ // reduction of large discriminated union types.
13344
+ const keyProperty = source.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive) ?
13345
+ find(getPropertiesOfType(source), p => isUnitType(getTypeOfSymbol(p))) :
13346
+ undefined;
13347
+ const keyPropertyType = keyProperty && getRegularTypeOfLiteralType(getTypeOfSymbol(keyProperty));
13334
13348
for (const target of types) {
13335
13349
if (source !== target) {
13350
+ if (count === 100000) {
13351
+ // After 100000 subtype checks we estimate the remaining amount of work by assuming the
13352
+ // same ratio of checks per element. If the estimated number of remaining type checks is
13353
+ // greater than 1M we deem the union type too complex to represent. This for example
13354
+ // caps union types at 1000 unique object types.
13355
+ const estimatedCount = (count / (len - i)) * len;
13356
+ if (estimatedCount > 1000000) {
13357
+ tracing?.instant(tracing.Phase.CheckTypes, "removeSubtypes_DepthLimit", { typeIds: types.map(t => t.id) });
13358
+ error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent);
13359
+ return undefined;
13360
+ }
13361
+ }
13362
+ count++;
13363
+ if (keyProperty && target.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive)) {
13364
+ const t = getTypeOfPropertyOfType(target, keyProperty.escapedName);
13365
+ if (t && isUnitType(t) && getRegularTypeOfLiteralType(t) !== keyPropertyType) {
13366
+ continue;
13367
+ }
13368
+ }
13336
13369
if (isTypeRelatedTo(source, target, strictSubtypeRelation) && (
13337
13370
!(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) ||
13338
13371
!(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) ||
@@ -13344,7 +13377,8 @@ namespace ts {
13344
13377
}
13345
13378
}
13346
13379
}
13347
- return true;
13380
+ subtypeReductionCache.set(id, types);
13381
+ return types;
13348
13382
}
13349
13383
13350
13384
function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags) {
@@ -13418,22 +13452,21 @@ namespace ts {
13418
13452
if (types.length === 1) {
13419
13453
return types[0];
13420
13454
}
13421
- const typeSet: Type[] = [];
13455
+ let typeSet: Type[] | undefined = [];
13422
13456
const includes = addTypesToUnion(typeSet, 0, types);
13423
13457
if (unionReduction !== UnionReduction.None) {
13424
13458
if (includes & TypeFlags.AnyOrUnknown) {
13425
13459
return includes & TypeFlags.Any ? includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : unknownType;
13426
13460
}
13427
- if (unionReduction & (UnionReduction.Literal | UnionReduction.Subtype)) {
13428
- if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol) || includes & TypeFlags.Void && includes & TypeFlags.Undefined) {
13429
- removeRedundantLiteralTypes(typeSet, includes);
13430
- }
13431
- if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) {
13432
- removeStringLiteralsMatchedByTemplateLiterals(typeSet);
13433
- }
13461
+ if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol) || includes & TypeFlags.Void && includes & TypeFlags.Undefined) {
13462
+ removeRedundantLiteralTypes(typeSet, includes);
13463
+ }
13464
+ if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) {
13465
+ removeStringLiteralsMatchedByTemplateLiterals(typeSet);
13434
13466
}
13435
- if (unionReduction & UnionReduction.Subtype && typeSet.length < 100) {
13436
- if (!removeSubtypes(typeSet, !!(includes & TypeFlags.Object))) {
13467
+ if (unionReduction === UnionReduction.Subtype) {
13468
+ typeSet = removeSubtypes(typeSet, !!(includes & TypeFlags.Object));
13469
+ if (!typeSet) {
13437
13470
return errorType;
13438
13471
}
13439
13472
}
0 commit comments