Skip to content

Commit 697935d

Browse files
authored
Restore ordering of operations involving type parameters and unions (#50116)
1 parent 040c121 commit 697935d

File tree

1 file changed

+21
-23
lines changed

1 file changed

+21
-23
lines changed

src/compiler/checker.ts

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19381,7 +19381,27 @@ namespace ts {
1938119381

1938219382
function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
1938319383
const saveErrorInfo = captureErrorCalculationState();
19384-
const result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState, saveErrorInfo);
19384+
let result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState, saveErrorInfo);
19385+
if (!result && (source.flags & TypeFlags.Intersection || source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.Union)) {
19386+
// The combined constraint of an intersection type is the intersection of the constraints of
19387+
// the constituents. When an intersection type contains instantiable types with union type
19388+
// constraints, there are situations where we need to examine the combined constraint. One is
19389+
// when the target is a union type. Another is when the intersection contains types belonging
19390+
// to one of the disjoint domains. For example, given type variables T and U, each with the
19391+
// constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and
19392+
// we need to check this constraint against a union on the target side. Also, given a type
19393+
// variable V constrained to 'string | number', 'V & number' has a combined constraint of
19394+
// 'string & number | number & number' which reduces to just 'number'.
19395+
// This also handles type parameters, as a type parameter with a union constraint compared against a union
19396+
// needs to have its constraint hoisted into an intersection with said type parameter, this way
19397+
// the type param can be compared with itself in the target (with the influence of its constraint to match other parts)
19398+
// For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)`
19399+
const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union));
19400+
if (constraint && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself
19401+
// TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this
19402+
result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
19403+
}
19404+
}
1938519405
if (result) {
1938619406
resetErrorInfo(saveErrorInfo);
1938719407
}
@@ -19440,28 +19460,6 @@ namespace ts {
1944019460
if (result = unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState)) {
1944119461
return result;
1944219462
}
19443-
if (source.flags & TypeFlags.Intersection || source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.Union) {
19444-
// The combined constraint of an intersection type is the intersection of the constraints of
19445-
// the constituents. When an intersection type contains instantiable types with union type
19446-
// constraints, there are situations where we need to examine the combined constraint. One is
19447-
// when the target is a union type. Another is when the intersection contains types belonging
19448-
// to one of the disjoint domains. For example, given type variables T and U, each with the
19449-
// constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and
19450-
// we need to check this constraint against a union on the target side. Also, given a type
19451-
// variable V constrained to 'string | number', 'V & number' has a combined constraint of
19452-
// 'string & number | number & number' which reduces to just 'number'.
19453-
// This also handles type parameters, as a type parameter with a union constraint compared against a union
19454-
// needs to have its constraint hoisted into an intersection with said type parameter, this way
19455-
// the type param can be compared with itself in the target (with the influence of its constraint to match other parts)
19456-
// For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)`
19457-
const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union));
19458-
if (constraint && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself
19459-
// TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this
19460-
if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) {
19461-
return result;
19462-
}
19463-
}
19464-
}
1946519463
// The ordered decomposition above doesn't handle all cases. Specifically, we also need to handle:
1946619464
// Source is instantiable (e.g. source has union or intersection constraint).
1946719465
// Source is an object, target is a union (e.g. { a, b: boolean } <=> { a, b: true } | { a, b: false }).

0 commit comments

Comments
 (0)