@@ -20778,6 +20778,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2077820778 let skipParentCounter = 0; // How many errors should be skipped 'above' in the elaboration pyramid
2077920779 let lastSkippedInfo: [Type, Type] | undefined;
2078020780 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;
2078120784
2078220785 Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking");
2078320786
@@ -20786,8 +20789,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2078620789 reportIncompatibleStack();
2078720790 }
2078820791 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);
2078920795 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));
2079120800 if (errorOutputContainer) {
2079220801 (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
2079320802 }
@@ -21420,6 +21429,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2142021429 // we need to deconstruct unions before intersections (because unions are always at the top),
2142121430 // and we need to handle "each" relations before "some" relations for the same kind of type.
2142221431 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+ }
2142321448 return relation === comparableRelation ?
2142421449 someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) :
2142521450 eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState);
@@ -21671,6 +21696,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2167121696 return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
2167221697 }
2167321698 }
21699+ if (relationCount <= 0) {
21700+ overflow = true;
21701+ return Ternary.False;
21702+ }
2167421703 if (!maybeKeys) {
2167521704 maybeKeys = [];
2167621705 maybeKeysSet = new Set();
@@ -21768,6 +21797,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2176821797 // A false result goes straight into global cache (when something is false under
2176921798 // assumptions it will also be false without assumptions)
2177021799 relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags);
21800+ relationCount--;
2177121801 resetMaybeStack(/*markAllAsSucceeded*/ false);
2177221802 }
2177321803 return result;
@@ -21777,6 +21807,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2177721807 maybeKeysSet.delete(maybeKeys[i]);
2177821808 if (markAllAsSucceeded) {
2177921809 relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags);
21810+ relationCount--;
2178021811 }
2178121812 }
2178221813 maybeCount = maybeStart;
0 commit comments