@@ -20778,6 +20778,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
20778
20778
let skipParentCounter = 0; // How many errors should be skipped 'above' in the elaboration pyramid
20779
20779
let lastSkippedInfo: [Type, Type] | undefined;
20780
20780
let incompatibleStack: DiagnosticAndArguments[] | undefined;
20781
+ // In Node.js, the maximum number of elements in a map is 2^24. We limit the number of entries an invocation
20782
+ // of checkTypeRelatedTo can add to a relation to 1/8th of its remaining capacity.
20783
+ let relationCount = (16_000_000 - relation.size) >> 3;
20781
20784
20782
20785
Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking");
20783
20786
@@ -20786,8 +20789,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
20786
20789
reportIncompatibleStack();
20787
20790
}
20788
20791
if (overflow) {
20792
+ // Record this relation as having failed such that we don't attempt the overflowing operation again.
20793
+ const id = getRelationKey(source, target, /*intersectionState*/ IntersectionState.None, relation, /*ignoreConstraints*/ false);
20794
+ relation.set(id, RelationComparisonResult.Reported | RelationComparisonResult.Failed);
20789
20795
tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth });
20790
- const diag = error(errorNode || currentNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target));
20796
+ const message = relationCount <= 0 ?
20797
+ Diagnostics.Excessive_complexity_comparing_types_0_and_1 :
20798
+ Diagnostics.Excessive_stack_depth_comparing_types_0_and_1;
20799
+ const diag = error(errorNode || currentNode, message, typeToString(source), typeToString(target));
20791
20800
if (errorOutputContainer) {
20792
20801
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
20793
20802
}
@@ -21420,6 +21429,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
21420
21429
// we need to deconstruct unions before intersections (because unions are always at the top),
21421
21430
// and we need to handle "each" relations before "some" relations for the same kind of type.
21422
21431
if (source.flags & TypeFlags.Union) {
21432
+ if (target.flags & TypeFlags.Union) {
21433
+ // Intersections of union types are normalized into unions of intersection types, and such normalized
21434
+ // unions can get very large and expensive to relate. The following fast path checks if the source union
21435
+ // originated in an intersection. If so, and if that intersection contains the target type, then we know
21436
+ // the result to be true (for any two types A and B, A & B is related to both A and B).
21437
+ const sourceOrigin = (source as UnionType).origin;
21438
+ if (sourceOrigin && sourceOrigin.flags & TypeFlags.Intersection && target.aliasSymbol && contains((sourceOrigin as IntersectionType).types, target)) {
21439
+ return Ternary.True;
21440
+ }
21441
+ // Similarly, in unions of unions the we preserve the original list of unions. This original list is often
21442
+ // much shorter than the normalized result, so we scan it in the following fast path.
21443
+ const targetOrigin = (target as UnionType).origin;
21444
+ if (targetOrigin && targetOrigin.flags & TypeFlags.Union && source.aliasSymbol && contains((targetOrigin as UnionType).types, source)) {
21445
+ return Ternary.True;
21446
+ }
21447
+ }
21423
21448
return relation === comparableRelation ?
21424
21449
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) :
21425
21450
eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState);
@@ -21671,6 +21696,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
21671
21696
return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
21672
21697
}
21673
21698
}
21699
+ if (relationCount <= 0) {
21700
+ overflow = true;
21701
+ return Ternary.False;
21702
+ }
21674
21703
if (!maybeKeys) {
21675
21704
maybeKeys = [];
21676
21705
maybeKeysSet = new Set();
@@ -21768,6 +21797,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
21768
21797
// A false result goes straight into global cache (when something is false under
21769
21798
// assumptions it will also be false without assumptions)
21770
21799
relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags);
21800
+ relationCount--;
21771
21801
resetMaybeStack(/*markAllAsSucceeded*/ false);
21772
21802
}
21773
21803
return result;
@@ -21777,6 +21807,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
21777
21807
maybeKeysSet.delete(maybeKeys[i]);
21778
21808
if (markAllAsSucceeded) {
21779
21809
relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags);
21810
+ relationCount--;
21780
21811
}
21781
21812
}
21782
21813
maybeCount = maybeStart;
0 commit comments