Skip to content

Commit ea0cc79

Browse files
committed
Merge pull request #5738 from Microsoft/unionIntersectionTypeInference
Improved union/intersection type inference
2 parents fb76dc9 + add5146 commit ea0cc79

9 files changed

+906
-1
lines changed

src/compiler/checker.ts

+46-1
Original file line numberDiff line numberDiff line change
@@ -6085,6 +6085,17 @@ namespace ts {
60856085
}
60866086

60876087
function inferFromTypes(source: Type, target: Type) {
6088+
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union ||
6089+
source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) {
6090+
// Source and target are both unions or both intersections. To improve the quality of
6091+
// inferences we first reduce the types by removing constituents that are identically
6092+
// matched by a constituent in the other type. For example, when inferring from
6093+
// 'string | string[]' to 'string | T', we reduce the types to 'string[]' and 'T'.
6094+
const reducedSource = reduceUnionOrIntersectionType(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target);
6095+
const reducedTarget = reduceUnionOrIntersectionType(<UnionOrIntersectionType>target, <UnionOrIntersectionType>source);
6096+
source = reducedSource;
6097+
target = reducedTarget;
6098+
}
60886099
if (target.flags & TypeFlags.TypeParameter) {
60896100
// If target is a type parameter, make an inference, unless the source type contains
60906101
// the anyFunctionType (the wildcard type that's used to avoid contextually typing functions).
@@ -6095,7 +6106,6 @@ namespace ts {
60956106
if (source.flags & TypeFlags.ContainsAnyFunctionType) {
60966107
return;
60976108
}
6098-
60996109
const typeParameters = context.typeParameters;
61006110
for (let i = 0; i < typeParameters.length; i++) {
61016111
if (target === typeParameters[i]) {
@@ -6243,6 +6253,41 @@ namespace ts {
62436253
}
62446254
}
62456255

6256+
function typeIdenticalToSomeType(source: Type, target: UnionOrIntersectionType): boolean {
6257+
for (const t of target.types) {
6258+
if (isTypeIdenticalTo(source, t)) {
6259+
return true;
6260+
}
6261+
}
6262+
return false;
6263+
}
6264+
6265+
/**
6266+
* Return the reduced form of the source type. This type is computed by by removing all source
6267+
* constituents that have an identical match in the target type.
6268+
*/
6269+
function reduceUnionOrIntersectionType(source: UnionOrIntersectionType, target: UnionOrIntersectionType) {
6270+
let sourceTypes = source.types;
6271+
let sourceIndex = 0;
6272+
let modified = false;
6273+
while (sourceIndex < sourceTypes.length) {
6274+
if (typeIdenticalToSomeType(sourceTypes[sourceIndex], target)) {
6275+
if (!modified) {
6276+
sourceTypes = sourceTypes.slice(0);
6277+
modified = true;
6278+
}
6279+
sourceTypes.splice(sourceIndex, 1);
6280+
}
6281+
else {
6282+
sourceIndex++;
6283+
}
6284+
}
6285+
if (modified) {
6286+
return source.flags & TypeFlags.Union ? getUnionType(sourceTypes, /*noSubtypeReduction*/ true) : getIntersectionType(sourceTypes);
6287+
}
6288+
return source;
6289+
}
6290+
62466291
function getInferenceCandidates(context: InferenceContext, index: number): Type[] {
62476292
const inferences = context.inferences[index];
62486293
return inferences.primary || inferences.secondary || emptyArray;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//// [unionAndIntersectionInference1.ts]
2+
// Repro from #2264
3+
4+
interface Y { 'i am a very certain type': Y }
5+
var y: Y = <Y>undefined;
6+
function destructure<a, r>(
7+
something: a | Y,
8+
haveValue: (value: a) => r,
9+
haveY: (value: Y) => r
10+
): r {
11+
return something === y ? haveY(y) : haveValue(<a>something);
12+
}
13+
14+
var value = Math.random() > 0.5 ? 'hey!' : <Y>undefined;
15+
16+
var result = destructure(value, text => 'string', y => 'other one'); // text: string, y: Y
17+
18+
// Repro from #4212
19+
20+
function isVoid<a>(value: void | a): value is void {
21+
return undefined;
22+
}
23+
24+
function isNonVoid<a>(value: void | a) : value is a {
25+
return undefined;
26+
}
27+
28+
function foo1<a>(value: void|a): void {
29+
if (isVoid(value)) {
30+
value; // value is void
31+
} else {
32+
value; // value is a
33+
}
34+
}
35+
36+
function baz1<a>(value: void|a): void {
37+
if (isNonVoid(value)) {
38+
value; // value is a
39+
} else {
40+
value; // value is void
41+
}
42+
}
43+
44+
// Repro from #5417
45+
46+
type Maybe<T> = T | void;
47+
48+
function get<U>(x: U | void): U {
49+
return null; // just an example
50+
}
51+
52+
let foo: Maybe<string>;
53+
get(foo).toUpperCase(); // Ok
54+
55+
// Repro from #5456
56+
57+
interface Man {
58+
walks: boolean;
59+
}
60+
61+
interface Bear {
62+
roars: boolean;
63+
}
64+
65+
interface Pig {
66+
oinks: boolean;
67+
}
68+
69+
declare function pigify<T>(y: T & Bear): T & Pig;
70+
declare var mbp: Man & Bear;
71+
72+
pigify(mbp).oinks; // OK, mbp is treated as Pig
73+
pigify(mbp).walks; // Ok, mbp is treated as Man
74+
75+
76+
//// [unionAndIntersectionInference1.js]
77+
// Repro from #2264
78+
var y = undefined;
79+
function destructure(something, haveValue, haveY) {
80+
return something === y ? haveY(y) : haveValue(something);
81+
}
82+
var value = Math.random() > 0.5 ? 'hey!' : undefined;
83+
var result = destructure(value, function (text) { return 'string'; }, function (y) { return 'other one'; }); // text: string, y: Y
84+
// Repro from #4212
85+
function isVoid(value) {
86+
return undefined;
87+
}
88+
function isNonVoid(value) {
89+
return undefined;
90+
}
91+
function foo1(value) {
92+
if (isVoid(value)) {
93+
value; // value is void
94+
}
95+
else {
96+
value; // value is a
97+
}
98+
}
99+
function baz1(value) {
100+
if (isNonVoid(value)) {
101+
value; // value is a
102+
}
103+
else {
104+
value; // value is void
105+
}
106+
}
107+
function get(x) {
108+
return null; // just an example
109+
}
110+
var foo;
111+
get(foo).toUpperCase(); // Ok
112+
pigify(mbp).oinks; // OK, mbp is treated as Pig
113+
pigify(mbp).walks; // Ok, mbp is treated as Man
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
=== tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts ===
2+
// Repro from #2264
3+
4+
interface Y { 'i am a very certain type': Y }
5+
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))
6+
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))
7+
8+
var y: Y = <Y>undefined;
9+
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 3, 3))
10+
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))
11+
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))
12+
>undefined : Symbol(undefined)
13+
14+
function destructure<a, r>(
15+
>destructure : Symbol(destructure, Decl(unionAndIntersectionInference1.ts, 3, 24))
16+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 4, 21))
17+
>r : Symbol(r, Decl(unionAndIntersectionInference1.ts, 4, 23))
18+
19+
something: a | Y,
20+
>something : Symbol(something, Decl(unionAndIntersectionInference1.ts, 4, 27))
21+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 4, 21))
22+
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))
23+
24+
haveValue: (value: a) => r,
25+
>haveValue : Symbol(haveValue, Decl(unionAndIntersectionInference1.ts, 5, 21))
26+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 6, 16))
27+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 4, 21))
28+
>r : Symbol(r, Decl(unionAndIntersectionInference1.ts, 4, 23))
29+
30+
haveY: (value: Y) => r
31+
>haveY : Symbol(haveY, Decl(unionAndIntersectionInference1.ts, 6, 31))
32+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 7, 12))
33+
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))
34+
>r : Symbol(r, Decl(unionAndIntersectionInference1.ts, 4, 23))
35+
36+
): r {
37+
>r : Symbol(r, Decl(unionAndIntersectionInference1.ts, 4, 23))
38+
39+
return something === y ? haveY(y) : haveValue(<a>something);
40+
>something : Symbol(something, Decl(unionAndIntersectionInference1.ts, 4, 27))
41+
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 3, 3))
42+
>haveY : Symbol(haveY, Decl(unionAndIntersectionInference1.ts, 6, 31))
43+
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 3, 3))
44+
>haveValue : Symbol(haveValue, Decl(unionAndIntersectionInference1.ts, 5, 21))
45+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 4, 21))
46+
>something : Symbol(something, Decl(unionAndIntersectionInference1.ts, 4, 27))
47+
}
48+
49+
var value = Math.random() > 0.5 ? 'hey!' : <Y>undefined;
50+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 12, 3))
51+
>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --))
52+
>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
53+
>random : Symbol(Math.random, Decl(lib.d.ts, --, --))
54+
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0))
55+
>undefined : Symbol(undefined)
56+
57+
var result = destructure(value, text => 'string', y => 'other one'); // text: string, y: Y
58+
>result : Symbol(result, Decl(unionAndIntersectionInference1.ts, 14, 3))
59+
>destructure : Symbol(destructure, Decl(unionAndIntersectionInference1.ts, 3, 24))
60+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 12, 3))
61+
>text : Symbol(text, Decl(unionAndIntersectionInference1.ts, 14, 31))
62+
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 14, 49))
63+
64+
// Repro from #4212
65+
66+
function isVoid<a>(value: void | a): value is void {
67+
>isVoid : Symbol(isVoid, Decl(unionAndIntersectionInference1.ts, 14, 68))
68+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 18, 16))
69+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 18, 19))
70+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 18, 16))
71+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 18, 19))
72+
73+
return undefined;
74+
>undefined : Symbol(undefined)
75+
}
76+
77+
function isNonVoid<a>(value: void | a) : value is a {
78+
>isNonVoid : Symbol(isNonVoid, Decl(unionAndIntersectionInference1.ts, 20, 1))
79+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 22, 19))
80+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 22, 22))
81+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 22, 19))
82+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 22, 22))
83+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 22, 19))
84+
85+
return undefined;
86+
>undefined : Symbol(undefined)
87+
}
88+
89+
function foo1<a>(value: void|a): void {
90+
>foo1 : Symbol(foo1, Decl(unionAndIntersectionInference1.ts, 24, 1))
91+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 26, 14))
92+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 26, 17))
93+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 26, 14))
94+
95+
if (isVoid(value)) {
96+
>isVoid : Symbol(isVoid, Decl(unionAndIntersectionInference1.ts, 14, 68))
97+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 26, 17))
98+
99+
value; // value is void
100+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 26, 17))
101+
102+
} else {
103+
value; // value is a
104+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 26, 17))
105+
}
106+
}
107+
108+
function baz1<a>(value: void|a): void {
109+
>baz1 : Symbol(baz1, Decl(unionAndIntersectionInference1.ts, 32, 1))
110+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 34, 14))
111+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 34, 17))
112+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 34, 14))
113+
114+
if (isNonVoid(value)) {
115+
>isNonVoid : Symbol(isNonVoid, Decl(unionAndIntersectionInference1.ts, 20, 1))
116+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 34, 17))
117+
118+
value; // value is a
119+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 34, 17))
120+
121+
} else {
122+
value; // value is void
123+
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 34, 17))
124+
}
125+
}
126+
127+
// Repro from #5417
128+
129+
type Maybe<T> = T | void;
130+
>Maybe : Symbol(Maybe, Decl(unionAndIntersectionInference1.ts, 40, 1))
131+
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 44, 11))
132+
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 44, 11))
133+
134+
function get<U>(x: U | void): U {
135+
>get : Symbol(get, Decl(unionAndIntersectionInference1.ts, 44, 25))
136+
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 46, 13))
137+
>x : Symbol(x, Decl(unionAndIntersectionInference1.ts, 46, 16))
138+
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 46, 13))
139+
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 46, 13))
140+
141+
return null; // just an example
142+
}
143+
144+
let foo: Maybe<string>;
145+
>foo : Symbol(foo, Decl(unionAndIntersectionInference1.ts, 50, 3))
146+
>Maybe : Symbol(Maybe, Decl(unionAndIntersectionInference1.ts, 40, 1))
147+
148+
get(foo).toUpperCase(); // Ok
149+
>get(foo).toUpperCase : Symbol(String.toUpperCase, Decl(lib.d.ts, --, --))
150+
>get : Symbol(get, Decl(unionAndIntersectionInference1.ts, 44, 25))
151+
>foo : Symbol(foo, Decl(unionAndIntersectionInference1.ts, 50, 3))
152+
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.d.ts, --, --))
153+
154+
// Repro from #5456
155+
156+
interface Man {
157+
>Man : Symbol(Man, Decl(unionAndIntersectionInference1.ts, 51, 23))
158+
159+
walks: boolean;
160+
>walks : Symbol(walks, Decl(unionAndIntersectionInference1.ts, 55, 15))
161+
}
162+
163+
interface Bear {
164+
>Bear : Symbol(Bear, Decl(unionAndIntersectionInference1.ts, 57, 1))
165+
166+
roars: boolean;
167+
>roars : Symbol(roars, Decl(unionAndIntersectionInference1.ts, 59, 16))
168+
}
169+
170+
interface Pig {
171+
>Pig : Symbol(Pig, Decl(unionAndIntersectionInference1.ts, 61, 1))
172+
173+
oinks: boolean;
174+
>oinks : Symbol(oinks, Decl(unionAndIntersectionInference1.ts, 63, 15))
175+
}
176+
177+
declare function pigify<T>(y: T & Bear): T & Pig;
178+
>pigify : Symbol(pigify, Decl(unionAndIntersectionInference1.ts, 65, 1))
179+
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 67, 24))
180+
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 67, 27))
181+
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 67, 24))
182+
>Bear : Symbol(Bear, Decl(unionAndIntersectionInference1.ts, 57, 1))
183+
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 67, 24))
184+
>Pig : Symbol(Pig, Decl(unionAndIntersectionInference1.ts, 61, 1))
185+
186+
declare var mbp: Man & Bear;
187+
>mbp : Symbol(mbp, Decl(unionAndIntersectionInference1.ts, 68, 11))
188+
>Man : Symbol(Man, Decl(unionAndIntersectionInference1.ts, 51, 23))
189+
>Bear : Symbol(Bear, Decl(unionAndIntersectionInference1.ts, 57, 1))
190+
191+
pigify(mbp).oinks; // OK, mbp is treated as Pig
192+
>pigify(mbp).oinks : Symbol(Pig.oinks, Decl(unionAndIntersectionInference1.ts, 63, 15))
193+
>pigify : Symbol(pigify, Decl(unionAndIntersectionInference1.ts, 65, 1))
194+
>mbp : Symbol(mbp, Decl(unionAndIntersectionInference1.ts, 68, 11))
195+
>oinks : Symbol(Pig.oinks, Decl(unionAndIntersectionInference1.ts, 63, 15))
196+
197+
pigify(mbp).walks; // Ok, mbp is treated as Man
198+
>pigify(mbp).walks : Symbol(Man.walks, Decl(unionAndIntersectionInference1.ts, 55, 15))
199+
>pigify : Symbol(pigify, Decl(unionAndIntersectionInference1.ts, 65, 1))
200+
>mbp : Symbol(mbp, Decl(unionAndIntersectionInference1.ts, 68, 11))
201+
>walks : Symbol(Man.walks, Decl(unionAndIntersectionInference1.ts, 55, 15))
202+

0 commit comments

Comments
 (0)