Skip to content

Commit 08946cd

Browse files
committed
Optimize comparing intersections with common members
1 parent 3b80ddc commit 08946cd

File tree

1 file changed

+36
-0
lines changed

1 file changed

+36
-0
lines changed

src/compiler/checker.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18809,6 +18809,39 @@ namespace ts {
1880918809
return Ternary.False;
1881018810
}
1881118811

18812+
// Before normalization: Intersections applied to unions create an explosion of types when normalized -
18813+
// handling those inner intersections can be very costly, so it can be beneficial to factor them out early.
18814+
// If both the source and target are intersections, we can remove the common types from them before formal normalization,
18815+
// and run a simplified comparison without those common members.
18816+
if (originalSource.flags & originalTarget.flags & TypeFlags.Intersection) {
18817+
const combinedTypeSet = new Map<number, number>();
18818+
forEach((originalSource as IntersectionType).types, t => combinedTypeSet.set(getTypeId(t), 1));
18819+
let hasOverlap = false;
18820+
forEach((originalTarget as IntersectionType).types, t => {
18821+
const id = getTypeId(t);
18822+
if (combinedTypeSet.has(id)) {
18823+
combinedTypeSet.set(getTypeId(t), 2);
18824+
hasOverlap = true;
18825+
}
18826+
});
18827+
if (hasOverlap) {
18828+
const filteredSource = filter((originalSource as IntersectionType).types, t => combinedTypeSet.get(getTypeId(t)) !== 2);
18829+
const filteredTarget = filter((originalTarget as IntersectionType).types, t => combinedTypeSet.get(getTypeId(t)) !== 2);
18830+
if (!length(filteredTarget)) {
18831+
return Ternary.True; // Source has all parts of target
18832+
}
18833+
if (length(filteredSource)) {
18834+
let result: Ternary;
18835+
if (result = isRelatedTo(getIntersectionType(filteredSource), getIntersectionType(filteredTarget), recursionFlags, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) {
18836+
return result;
18837+
}
18838+
// In the false case, we may still be assignable at the structural level, rather than the algebraic level
18839+
}
18840+
// Even if every member of the source is in the target but the target still has members left, the source may still be assignable
18841+
// 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
18842+
}
18843+
}
18844+
1881218845
// Normalize the source and target types: Turn fresh literal types into regular literal types,
1881318846
// turn deferred type references into regular type references, simplify indexed access and
1881418847
// conditional types, and resolve substitution types to either the substitution (on the source
@@ -19309,6 +19342,9 @@ namespace ts {
1930919342
// equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion
1931019343
// and issue an error. Otherwise, actually compare the structure of the two types.
1931119344
function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, recursionFlags: RecursionFlags): Ternary {
19345+
if (relation.size > 2 ** 20) {
19346+
debugger;
19347+
}
1931219348
if (overflow) {
1931319349
return Ternary.False;
1931419350
}

0 commit comments

Comments
 (0)