Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize comparing intersections with common members #50329

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21080,6 +21080,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
return isArrayOrTupleType(target);
}

if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) {
if (reportErrors) {
reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target));
Expand Down Expand Up @@ -21120,6 +21121,40 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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) {
// Set 1 for elements occuring in the source, then 2 for elements occuring in both source and target
const combinedTypeSet = new Map<number, number>();
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
Expand Down