Skip to content

Fix union/union or intersection/intersection type inference #5942

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

Merged
merged 3 commits into from
Dec 5, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 30 additions & 31 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6087,14 +6087,25 @@ 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(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target);
const reducedTarget = reduceUnionOrIntersectionType(<UnionOrIntersectionType>target, <UnionOrIntersectionType>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.
// When inferring from a type to itself we effectively find all type parameter
// occurrences within that type and infer themselves as their type arguments.
let matchingTypes: Type[];
for (const t of (<UnionOrIntersectionType>target).types) {
if (typeIdenticalToSomeType(t, (<UnionOrIntersectionType>source).types)) {
(matchingTypes || (matchingTypes = [])).push(t);
inferFromTypes(t, t);
}
}
// Next, 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(<UnionOrIntersectionType>source, matchingTypes);
target = removeTypesFromUnionOrIntersection(<UnionOrIntersectionType>target, matchingTypes);
}
}
if (target.flags & TypeFlags.TypeParameter) {
// If target is a type parameter, make an inference, unless the source type contains
Expand Down Expand Up @@ -6256,39 +6267,27 @@ 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;
}
}
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.
* 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[] {
Expand Down
14 changes: 14 additions & 0 deletions tests/baselines/reference/recursiveUnionTypeInference.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//// [recursiveUnionTypeInference.ts]
interface Foo<T> {
x: T;
}

function bar<T>(x: Foo<T> | string): T {
return bar(x);
}


//// [recursiveUnionTypeInference.js]
function bar(x) {
return bar(x);
}
23 changes: 23 additions & 0 deletions tests/baselines/reference/recursiveUnionTypeInference.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
=== tests/cases/compiler/recursiveUnionTypeInference.ts ===
interface Foo<T> {
>Foo : Symbol(Foo, Decl(recursiveUnionTypeInference.ts, 0, 0))
>T : Symbol(T, Decl(recursiveUnionTypeInference.ts, 0, 14))

x: T;
>x : Symbol(x, Decl(recursiveUnionTypeInference.ts, 0, 18))
>T : Symbol(T, Decl(recursiveUnionTypeInference.ts, 0, 14))
}

function bar<T>(x: Foo<T> | string): T {
>bar : Symbol(bar, Decl(recursiveUnionTypeInference.ts, 2, 1))
>T : Symbol(T, Decl(recursiveUnionTypeInference.ts, 4, 13))
>x : Symbol(x, Decl(recursiveUnionTypeInference.ts, 4, 16))
>Foo : Symbol(Foo, Decl(recursiveUnionTypeInference.ts, 0, 0))
>T : Symbol(T, Decl(recursiveUnionTypeInference.ts, 4, 13))
>T : Symbol(T, Decl(recursiveUnionTypeInference.ts, 4, 13))

return bar(x);
>bar : Symbol(bar, Decl(recursiveUnionTypeInference.ts, 2, 1))
>x : Symbol(x, Decl(recursiveUnionTypeInference.ts, 4, 16))
}

24 changes: 24 additions & 0 deletions tests/baselines/reference/recursiveUnionTypeInference.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
=== tests/cases/compiler/recursiveUnionTypeInference.ts ===
interface Foo<T> {
>Foo : Foo<T>
>T : T

x: T;
>x : T
>T : T
}

function bar<T>(x: Foo<T> | string): T {
>bar : <T>(x: Foo<T> | string) => T
>T : T
>x : Foo<T> | string
>Foo : Foo<T>
>T : T
>T : T

return bar(x);
>bar(x) : T
>bar : <T>(x: Foo<T> | string) => T
>x : Foo<T> | string
}

7 changes: 7 additions & 0 deletions tests/cases/compiler/recursiveUnionTypeInference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface Foo<T> {
x: T;
}

function bar<T>(x: Foo<T> | string): T {
return bar(x);
}