Skip to content

Commit

Permalink
Merge pull request #5738 from Microsoft/unionIntersectionTypeInference
Browse files Browse the repository at this point in the history
Improved union/intersection type inference
  • Loading branch information
ahejlsberg committed Nov 24, 2015
2 parents fb76dc9 + add5146 commit ea0cc79
Show file tree
Hide file tree
Showing 9 changed files with 906 additions and 1 deletion.
47 changes: 46 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6085,6 +6085,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(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target);
const reducedTarget = reduceUnionOrIntersectionType(<UnionOrIntersectionType>target, <UnionOrIntersectionType>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).
Expand All @@ -6095,7 +6106,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]) {
Expand Down Expand Up @@ -6243,6 +6253,41 @@ namespace ts {
}
}

function typeIdenticalToSomeType(source: Type, target: UnionOrIntersectionType): boolean {
for (const 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;
Expand Down
113 changes: 113 additions & 0 deletions tests/baselines/reference/unionAndIntersectionInference1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//// [unionAndIntersectionInference1.ts]
// Repro from #2264

interface Y { 'i am a very certain type': Y }
var y: Y = <Y>undefined;
function destructure<a, r>(
something: a | Y,
haveValue: (value: a) => r,
haveY: (value: Y) => r
): r {
return something === y ? haveY(y) : haveValue(<a>something);
}

var value = Math.random() > 0.5 ? 'hey!' : <Y>undefined;

var result = destructure(value, text => 'string', y => 'other one'); // text: string, y: Y

// Repro from #4212

function isVoid<a>(value: void | a): value is void {
return undefined;
}

function isNonVoid<a>(value: void | a) : value is a {
return undefined;
}

function foo1<a>(value: void|a): void {
if (isVoid(value)) {
value; // value is void
} else {
value; // value is a
}
}

function baz1<a>(value: void|a): void {
if (isNonVoid(value)) {
value; // value is a
} else {
value; // value is void
}
}

// Repro from #5417

type Maybe<T> = T | void;

function get<U>(x: U | void): U {
return null; // just an example
}

let foo: Maybe<string>;
get(foo).toUpperCase(); // Ok

// Repro from #5456

interface Man {
walks: boolean;
}

interface Bear {
roars: boolean;
}

interface Pig {
oinks: boolean;
}

declare function pigify<T>(y: T & Bear): T & Pig;
declare var mbp: Man & Bear;

pigify(mbp).oinks; // OK, mbp is treated as Pig
pigify(mbp).walks; // Ok, mbp is treated as Man


//// [unionAndIntersectionInference1.js]
// Repro from #2264
var y = undefined;
function destructure(something, haveValue, haveY) {
return something === y ? haveY(y) : haveValue(something);
}
var value = Math.random() > 0.5 ? 'hey!' : undefined;
var result = destructure(value, function (text) { return 'string'; }, function (y) { return 'other one'; }); // text: string, y: Y
// Repro from #4212
function isVoid(value) {
return undefined;
}
function isNonVoid(value) {
return undefined;
}
function foo1(value) {
if (isVoid(value)) {
value; // value is void
}
else {
value; // value is a
}
}
function baz1(value) {
if (isNonVoid(value)) {
value; // value is a
}
else {
value; // value is void
}
}
function get(x) {
return null; // just an example
}
var foo;
get(foo).toUpperCase(); // Ok
pigify(mbp).oinks; // OK, mbp is treated as Pig
pigify(mbp).walks; // Ok, mbp is treated as Man
202 changes: 202 additions & 0 deletions tests/baselines/reference/unionAndIntersectionInference1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
=== tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts ===
// Repro from #2264

interface Y { 'i am a very certain type': Y }
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))

var y: Y = <Y>undefined;
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 3, 3))
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))
>undefined : Symbol(undefined)

function destructure<a, r>(
>destructure : Symbol(destructure, Decl(unionAndIntersectionInference1.ts, 3, 24))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 4, 21))
>r : Symbol(r, Decl(unionAndIntersectionInference1.ts, 4, 23))

something: a | Y,
>something : Symbol(something, Decl(unionAndIntersectionInference1.ts, 4, 27))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 4, 21))
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))

haveValue: (value: a) => r,
>haveValue : Symbol(haveValue, Decl(unionAndIntersectionInference1.ts, 5, 21))
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 6, 16))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 4, 21))
>r : Symbol(r, Decl(unionAndIntersectionInference1.ts, 4, 23))

haveY: (value: Y) => r
>haveY : Symbol(haveY, Decl(unionAndIntersectionInference1.ts, 6, 31))
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 7, 12))
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))
>r : Symbol(r, Decl(unionAndIntersectionInference1.ts, 4, 23))

): r {
>r : Symbol(r, Decl(unionAndIntersectionInference1.ts, 4, 23))

return something === y ? haveY(y) : haveValue(<a>something);
>something : Symbol(something, Decl(unionAndIntersectionInference1.ts, 4, 27))
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 3, 3))
>haveY : Symbol(haveY, Decl(unionAndIntersectionInference1.ts, 6, 31))
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 3, 3))
>haveValue : Symbol(haveValue, Decl(unionAndIntersectionInference1.ts, 5, 21))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 4, 21))
>something : Symbol(something, Decl(unionAndIntersectionInference1.ts, 4, 27))
}

var value = Math.random() > 0.5 ? 'hey!' : <Y>undefined;
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 12, 3))
>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.d.ts, --, --))
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))
>undefined : Symbol(undefined)

var result = destructure(value, text => 'string', y => 'other one'); // text: string, y: Y
>result : Symbol(result, Decl(unionAndIntersectionInference1.ts, 14, 3))
>destructure : Symbol(destructure, Decl(unionAndIntersectionInference1.ts, 3, 24))
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 12, 3))
>text : Symbol(text, Decl(unionAndIntersectionInference1.ts, 14, 31))
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 14, 49))

// Repro from #4212

function isVoid<a>(value: void | a): value is void {
>isVoid : Symbol(isVoid, Decl(unionAndIntersectionInference1.ts, 14, 68))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 18, 16))
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 18, 19))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 18, 16))
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 18, 19))

return undefined;
>undefined : Symbol(undefined)
}

function isNonVoid<a>(value: void | a) : value is a {
>isNonVoid : Symbol(isNonVoid, Decl(unionAndIntersectionInference1.ts, 20, 1))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 22, 19))
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 22, 22))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 22, 19))
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 22, 22))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 22, 19))

return undefined;
>undefined : Symbol(undefined)
}

function foo1<a>(value: void|a): void {
>foo1 : Symbol(foo1, Decl(unionAndIntersectionInference1.ts, 24, 1))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 26, 14))
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 26, 17))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 26, 14))

if (isVoid(value)) {
>isVoid : Symbol(isVoid, Decl(unionAndIntersectionInference1.ts, 14, 68))
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 26, 17))

value; // value is void
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 26, 17))

} else {
value; // value is a
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 26, 17))
}
}

function baz1<a>(value: void|a): void {
>baz1 : Symbol(baz1, Decl(unionAndIntersectionInference1.ts, 32, 1))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 34, 14))
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 34, 17))
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 34, 14))

if (isNonVoid(value)) {
>isNonVoid : Symbol(isNonVoid, Decl(unionAndIntersectionInference1.ts, 20, 1))
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 34, 17))

value; // value is a
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 34, 17))

} else {
value; // value is void
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 34, 17))
}
}

// Repro from #5417

type Maybe<T> = T | void;
>Maybe : Symbol(Maybe, Decl(unionAndIntersectionInference1.ts, 40, 1))
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 44, 11))
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 44, 11))

function get<U>(x: U | void): U {
>get : Symbol(get, Decl(unionAndIntersectionInference1.ts, 44, 25))
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 46, 13))
>x : Symbol(x, Decl(unionAndIntersectionInference1.ts, 46, 16))
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 46, 13))
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 46, 13))

return null; // just an example
}

let foo: Maybe<string>;
>foo : Symbol(foo, Decl(unionAndIntersectionInference1.ts, 50, 3))
>Maybe : Symbol(Maybe, Decl(unionAndIntersectionInference1.ts, 40, 1))

get(foo).toUpperCase(); // Ok
>get(foo).toUpperCase : Symbol(String.toUpperCase, Decl(lib.d.ts, --, --))
>get : Symbol(get, Decl(unionAndIntersectionInference1.ts, 44, 25))
>foo : Symbol(foo, Decl(unionAndIntersectionInference1.ts, 50, 3))
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.d.ts, --, --))

// Repro from #5456

interface Man {
>Man : Symbol(Man, Decl(unionAndIntersectionInference1.ts, 51, 23))

walks: boolean;
>walks : Symbol(walks, Decl(unionAndIntersectionInference1.ts, 55, 15))
}

interface Bear {
>Bear : Symbol(Bear, Decl(unionAndIntersectionInference1.ts, 57, 1))

roars: boolean;
>roars : Symbol(roars, Decl(unionAndIntersectionInference1.ts, 59, 16))
}

interface Pig {
>Pig : Symbol(Pig, Decl(unionAndIntersectionInference1.ts, 61, 1))

oinks: boolean;
>oinks : Symbol(oinks, Decl(unionAndIntersectionInference1.ts, 63, 15))
}

declare function pigify<T>(y: T & Bear): T & Pig;
>pigify : Symbol(pigify, Decl(unionAndIntersectionInference1.ts, 65, 1))
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 67, 24))
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 67, 27))
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 67, 24))
>Bear : Symbol(Bear, Decl(unionAndIntersectionInference1.ts, 57, 1))
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 67, 24))
>Pig : Symbol(Pig, Decl(unionAndIntersectionInference1.ts, 61, 1))

declare var mbp: Man & Bear;
>mbp : Symbol(mbp, Decl(unionAndIntersectionInference1.ts, 68, 11))
>Man : Symbol(Man, Decl(unionAndIntersectionInference1.ts, 51, 23))
>Bear : Symbol(Bear, Decl(unionAndIntersectionInference1.ts, 57, 1))

pigify(mbp).oinks; // OK, mbp is treated as Pig
>pigify(mbp).oinks : Symbol(Pig.oinks, Decl(unionAndIntersectionInference1.ts, 63, 15))
>pigify : Symbol(pigify, Decl(unionAndIntersectionInference1.ts, 65, 1))
>mbp : Symbol(mbp, Decl(unionAndIntersectionInference1.ts, 68, 11))
>oinks : Symbol(Pig.oinks, Decl(unionAndIntersectionInference1.ts, 63, 15))

pigify(mbp).walks; // Ok, mbp is treated as Man
>pigify(mbp).walks : Symbol(Man.walks, Decl(unionAndIntersectionInference1.ts, 55, 15))
>pigify : Symbol(pigify, Decl(unionAndIntersectionInference1.ts, 65, 1))
>mbp : Symbol(mbp, Decl(unionAndIntersectionInference1.ts, 68, 11))
>walks : Symbol(Man.walks, Decl(unionAndIntersectionInference1.ts, 55, 15))

Loading

0 comments on commit ea0cc79

Please sign in to comment.