From e6259a557075beb808b264ff842e5b6c30d3fc9a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 4 Dec 2015 16:04:47 -0800 Subject: [PATCH] Fix union/union and intersection/intersection type inference --- src/compiler/checker.ts | 59 +++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a4816013753..68ae6b20456 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6087,14 +6087,23 @@ namespace ts { function inferFromTypes(source: Type, target: Type) { if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union || source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) { - // Source and target are both unions or both intersections. To improve the quality of - // inferences we first reduce the types by removing constituents that are identically - // matched by a constituent in the other type. For example, when inferring from - // 'string | string[]' to 'string | T', we reduce the types to 'string[]' and 'T'. - const reducedSource = reduceUnionOrIntersectionType(source, target); - const reducedTarget = reduceUnionOrIntersectionType(target, source); - source = reducedSource; - target = reducedTarget; + // Source and target are both unions or both intersections. First, find each + // target constituent type that has an identically matching source constituent + // type, and for each such target constituent type, infer from the type to itself. + let matchingTypes: Type[]; + for (const t of (target).types) { + if (typeIdenticalToSomeType(t, (source).types)) { + (matchingTypes || (matchingTypes = [])).push(t); + inferFromTypes(t, t); + } + } + // To improve the quality of inferences, reduce the source and target types by + // removing the identically matched constituents. For example, when inferring from + // 'string | string[]' to 'string | T' we reduce the types to 'string[]' and 'T'. + if (matchingTypes) { + source = removeTypesFromUnionOrIntersection(source, matchingTypes); + target = removeTypesFromUnionOrIntersection(target, matchingTypes); + } } if (target.flags & TypeFlags.TypeParameter) { // If target is a type parameter, make an inference, unless the source type contains @@ -6256,9 +6265,9 @@ namespace ts { } } - function typeIdenticalToSomeType(source: Type, target: UnionOrIntersectionType): boolean { - for (const t of target.types) { - if (isTypeIdenticalTo(source, t)) { + function typeIdenticalToSomeType(type: Type, types: Type[]): boolean { + for (const t of types) { + if (isTypeIdenticalTo(t, type)) { return true; } } @@ -6266,29 +6275,17 @@ namespace ts { } /** - * Return the reduced form of the source type. This type is computed by by removing all source - * constituents that have an identical match in the target type. + * Return a new union or intersection type computed by removing a given set of types + * from a given union or intersection type. */ - function reduceUnionOrIntersectionType(source: UnionOrIntersectionType, target: UnionOrIntersectionType) { - let sourceTypes = source.types; - let sourceIndex = 0; - let modified = false; - while (sourceIndex < sourceTypes.length) { - if (typeIdenticalToSomeType(sourceTypes[sourceIndex], target)) { - if (!modified) { - sourceTypes = sourceTypes.slice(0); - modified = true; - } - sourceTypes.splice(sourceIndex, 1); - } - else { - sourceIndex++; + function removeTypesFromUnionOrIntersection(type: UnionOrIntersectionType, typesToRemove: Type[]) { + const reducedTypes: Type[] = []; + for (const t of type.types) { + if (!typeIdenticalToSomeType(t, typesToRemove)) { + reducedTypes.push(t); } } - if (modified) { - return source.flags & TypeFlags.Union ? getUnionType(sourceTypes, /*noSubtypeReduction*/ true) : getIntersectionType(sourceTypes); - } - return source; + return type.flags & TypeFlags.Union ? getUnionType(reducedTypes, /*noSubtypeReduction*/ true) : getIntersectionType(reducedTypes); } function getInferenceCandidates(context: InferenceContext, index: number): Type[] {