Skip to content

Commit b9f372e

Browse files
authored
Generalize the fastpath for comparisons of unions which are correspondences (#41903)
* Generalize the fastpath for comparisons of unions which are correspondences to unions resulting from the application of intersections * Add comment
1 parent 422fd19 commit b9f372e

File tree

1 file changed

+19
-2
lines changed

1 file changed

+19
-2
lines changed

src/compiler/checker.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -17292,14 +17292,31 @@ namespace ts {
1729217292
return Ternary.False;
1729317293
}
1729417294

17295+
function getUndefinedStrippedTargetIfNeeded(source: Type, target: Type) {
17296+
// As a builtin type, `undefined` is a very low type ID - making it almsot always first, making this a very fast check to see
17297+
// if we need to strip `undefined` from the target
17298+
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union &&
17299+
!((source as UnionType).types[0].flags & TypeFlags.Undefined) && (target as UnionType).types[0].flags & TypeFlags.Undefined) {
17300+
return extractTypesOfKind(target, ~TypeFlags.Undefined);
17301+
}
17302+
return target;
17303+
}
17304+
1729517305
function eachTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
1729617306
let result = Ternary.True;
1729717307
const sourceTypes = source.types;
17308+
// We strip `undefined` from the target if the `source` trivially doesn't contain it for our correspondence-checking fastpath
17309+
// since `undefined` is frequently added by optionality and would otherwise spoil a potentially useful correspondence
17310+
const undefinedStrippedTarget = getUndefinedStrippedTargetIfNeeded(source, target as UnionType);
1729817311
for (let i = 0; i < sourceTypes.length; i++) {
1729917312
const sourceType = sourceTypes[i];
17300-
if (target.flags & TypeFlags.Union && (target as UnionType).types.length === sourceTypes.length) {
17313+
if (undefinedStrippedTarget.flags & TypeFlags.Union && sourceTypes.length >= (undefinedStrippedTarget as UnionType).types.length && sourceTypes.length % (undefinedStrippedTarget as UnionType).types.length === 0) {
1730117314
// many unions are mappings of one another; in such cases, simply comparing members at the same index can shortcut the comparison
17302-
const related = isRelatedTo(sourceType, (target as UnionType).types[i], /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
17315+
// such unions will have identical lengths, and their corresponding elements will match up. Another common scenario is where a large
17316+
// union has a union of objects intersected with it. In such cases, if the input was, eg `("a" | "b" | "c") & (string | boolean | {} | {whatever})`,
17317+
// the result will have the structure `"a" | "b" | "c" | "a" & {} | "b" & {} | "c" & {} | "a" & {whatever} | "b" & {whatever} | "c" & {whatever}`
17318+
// - the resulting union has a length which is a multiple of the original union, and the elements correspond modulo the length of the original union
17319+
const related = isRelatedTo(sourceType, (undefinedStrippedTarget as UnionType).types[i % (undefinedStrippedTarget as UnionType).types.length], /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
1730317320
if (related) {
1730417321
result &= related;
1730517322
continue;

0 commit comments

Comments
 (0)