From 08946cd936fb7f849259c966403cedec1ec03b1b Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 16 Aug 2022 17:28:13 -0700 Subject: [PATCH 1/2] Optimize comparing intersections with common members --- src/compiler/checker.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8d53b7d1c4eb5..7383a9c2e32a6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18809,6 +18809,39 @@ namespace ts { return Ternary.False; } + // Before normalization: Intersections applied to unions create an explosion of types when normalized - + // handling those inner intersections can be very costly, so it can be beneficial to factor them out early. + // If both the source and target are intersections, we can remove the common types from them before formal normalization, + // and run a simplified comparison without those common members. + if (originalSource.flags & originalTarget.flags & TypeFlags.Intersection) { + const combinedTypeSet = new Map(); + forEach((originalSource as IntersectionType).types, t => combinedTypeSet.set(getTypeId(t), 1)); + let hasOverlap = false; + forEach((originalTarget as IntersectionType).types, t => { + const id = getTypeId(t); + if (combinedTypeSet.has(id)) { + combinedTypeSet.set(getTypeId(t), 2); + hasOverlap = true; + } + }); + if (hasOverlap) { + const filteredSource = filter((originalSource as IntersectionType).types, t => combinedTypeSet.get(getTypeId(t)) !== 2); + const filteredTarget = filter((originalTarget as IntersectionType).types, t => combinedTypeSet.get(getTypeId(t)) !== 2); + if (!length(filteredTarget)) { + return Ternary.True; // Source has all parts of target + } + if (length(filteredSource)) { + let result: Ternary; + if (result = isRelatedTo(getIntersectionType(filteredSource), getIntersectionType(filteredTarget), recursionFlags, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { + return result; + } + // In the false case, we may still be assignable at the structural level, rather than the algebraic level + } + // Even if every member of the source is in the target but the target still has members left, the source may still be assignable + // to the target, either if some member of the original source is assignable to the other members of the target, or if there is structural assignability + } + } + // Normalize the source and target types: Turn fresh literal types into regular literal types, // turn deferred type references into regular type references, simplify indexed access and // conditional types, and resolve substitution types to either the substitution (on the source @@ -19309,6 +19342,9 @@ namespace ts { // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion // and issue an error. Otherwise, actually compare the structure of the two types. function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, recursionFlags: RecursionFlags): Ternary { + if (relation.size > 2 ** 20) { + debugger; + } if (overflow) { return Ternary.False; } From 504fe1574aec2c77f903180bbe27ecefa6b88793 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 19 Aug 2022 10:56:13 -0700 Subject: [PATCH 2/2] PR feedback --- src/compiler/checker.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7383a9c2e32a6..0ff3215a87333 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18814,6 +18814,7 @@ namespace ts { // If both the source and target are intersections, we can remove the common types from them before formal normalization, // and run a simplified comparison without those common members. if (originalSource.flags & originalTarget.flags & TypeFlags.Intersection) { + // Set 1 for elements occuring in the source, then 2 for elements occuring in both source and target const combinedTypeSet = new Map(); forEach((originalSource as IntersectionType).types, t => combinedTypeSet.set(getTypeId(t), 1)); let hasOverlap = false; @@ -19342,9 +19343,6 @@ namespace ts { // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion // and issue an error. Otherwise, actually compare the structure of the two types. function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, recursionFlags: RecursionFlags): Ternary { - if (relation.size > 2 ** 20) { - debugger; - } if (overflow) { return Ternary.False; }