diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 009447df19a..0a79b148da6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6086,6 +6086,17 @@ 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; + } if (target.flags & TypeFlags.TypeParameter) { // If target is a type parameter, make an inference, unless the source type contains // the anyFunctionType (the wildcard type that's used to avoid contextually typing functions). @@ -6096,7 +6107,6 @@ namespace ts { if (source.flags & TypeFlags.ContainsAnyFunctionType) { return; } - const typeParameters = context.typeParameters; for (let i = 0; i < typeParameters.length; i++) { if (target === typeParameters[i]) { @@ -6244,6 +6254,41 @@ namespace ts { } } + function typeIdenticalToSomeType(source: Type, target: UnionOrIntersectionType): boolean { + for (let t of target.types) { + if (isTypeIdenticalTo(source, t)) { + return true; + } + } + return false; + } + + /** + * 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. + */ + 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++; + } + } + if (modified) { + return source.flags & TypeFlags.Union ? getUnionType(sourceTypes, /*noSubtypeReduction*/ true) : getIntersectionType(sourceTypes); + } + return source; + } + function getInferenceCandidates(context: InferenceContext, index: number): Type[] { const inferences = context.inferences[index]; return inferences.primary || inferences.secondary || emptyArray;