From e0c96cde6d7d25270c4265d7c6c869cffb332870 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 31 Jul 2022 16:50:53 -0400 Subject: [PATCH] Restore ordering of operations involving type parameters and unions --- src/compiler/checker.ts | 44 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a6f21c13f8251..febea9b912eb2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19383,7 +19383,27 @@ namespace ts { function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { const saveErrorInfo = captureErrorCalculationState(); - const result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState, saveErrorInfo); + let result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState, saveErrorInfo); + if (!result && (source.flags & TypeFlags.Intersection || source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.Union)) { + // The combined constraint of an intersection type is the intersection of the constraints of + // the constituents. When an intersection type contains instantiable types with union type + // constraints, there are situations where we need to examine the combined constraint. One is + // when the target is a union type. Another is when the intersection contains types belonging + // to one of the disjoint domains. For example, given type variables T and U, each with the + // constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and + // we need to check this constraint against a union on the target side. Also, given a type + // variable V constrained to 'string | number', 'V & number' has a combined constraint of + // 'string & number | number & number' which reduces to just 'number'. + // This also handles type parameters, as a type parameter with a union constraint compared against a union + // needs to have its constraint hoisted into an intersection with said type parameter, this way + // the type param can be compared with itself in the target (with the influence of its constraint to match other parts) + // For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)` + const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union)); + if (constraint && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself + // TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this + result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); + } + } if (result) { resetErrorInfo(saveErrorInfo); } @@ -19442,28 +19462,6 @@ namespace ts { if (result = unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState)) { return result; } - if (source.flags & TypeFlags.Intersection || source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.Union) { - // The combined constraint of an intersection type is the intersection of the constraints of - // the constituents. When an intersection type contains instantiable types with union type - // constraints, there are situations where we need to examine the combined constraint. One is - // when the target is a union type. Another is when the intersection contains types belonging - // to one of the disjoint domains. For example, given type variables T and U, each with the - // constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and - // we need to check this constraint against a union on the target side. Also, given a type - // variable V constrained to 'string | number', 'V & number' has a combined constraint of - // 'string & number | number & number' which reduces to just 'number'. - // This also handles type parameters, as a type parameter with a union constraint compared against a union - // needs to have its constraint hoisted into an intersection with said type parameter, this way - // the type param can be compared with itself in the target (with the influence of its constraint to match other parts) - // For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)` - const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union)); - if (constraint && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself - // TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this - if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { - return result; - } - } - } // The ordered decomposition above doesn't handle all cases. Specifically, we also need to handle: // Source is instantiable (e.g. source has union or intersection constraint). // Source is an object, target is a union (e.g. { a, b: boolean } <=> { a, b: true } | { a, b: false }).