Skip to content

Commit b022893

Browse files
ahejlsbergvladima
authored andcommitted
Merge pull request #5737 from Microsoft/unionIntersectionTypeInference
Improved union/intersection type inference Conflicts: src/compiler/checker.ts
1 parent a1ff917 commit b022893

9 files changed

+909
-3
lines changed

src/compiler/checker.ts

+49-3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ namespace ts {
4343
let emptyArray: any[] = [];
4444
let emptySymbols: SymbolTable = {};
4545

46+
let jsxElementClassType: Type = undefined;
47+
4648
let compilerOptions = host.getCompilerOptions();
4749
let languageVersion = compilerOptions.target || ScriptTarget.ES3;
4850
let modulekind = compilerOptions.module ? compilerOptions.module : languageVersion === ScriptTarget.ES6 ? ModuleKind.ES6 : ModuleKind.None;
@@ -5928,6 +5930,17 @@ namespace ts {
59285930
}
59295931

59305932
function inferFromTypes(source: Type, target: Type) {
5933+
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union ||
5934+
source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) {
5935+
// Source and target are both unions or both intersections. To improve the quality of
5936+
// inferences we first reduce the types by removing constituents that are identically
5937+
// matched by a constituent in the other type. For example, when inferring from
5938+
// 'string | string[]' to 'string | T', we reduce the types to 'string[]' and 'T'.
5939+
const reducedSource = reduceUnionOrIntersectionType(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target);
5940+
const reducedTarget = reduceUnionOrIntersectionType(<UnionOrIntersectionType>target, <UnionOrIntersectionType>source);
5941+
source = reducedSource;
5942+
target = reducedTarget;
5943+
}
59315944
if (target.flags & TypeFlags.TypeParameter) {
59325945
// If target is a type parameter, make an inference, unless the source type contains
59335946
// the anyFunctionType (the wildcard type that's used to avoid contextually typing functions).
@@ -5938,8 +5951,7 @@ namespace ts {
59385951
if (source.flags & TypeFlags.ContainsAnyFunctionType) {
59395952
return;
59405953
}
5941-
5942-
let typeParameters = context.typeParameters;
5954+
const typeParameters = context.typeParameters;
59435955
for (let i = 0; i < typeParameters.length; i++) {
59445956
if (target === typeParameters[i]) {
59455957
let inferences = context.inferences[i];
@@ -6086,6 +6098,41 @@ namespace ts {
60866098
}
60876099
}
60886100

6101+
function typeIdenticalToSomeType(source: Type, target: UnionOrIntersectionType): boolean {
6102+
for (const t of target.types) {
6103+
if (isTypeIdenticalTo(source, t)) {
6104+
return true;
6105+
}
6106+
}
6107+
return false;
6108+
}
6109+
6110+
/**
6111+
* Return the reduced form of the source type. This type is computed by by removing all source
6112+
* constituents that have an identical match in the target type.
6113+
*/
6114+
function reduceUnionOrIntersectionType(source: UnionOrIntersectionType, target: UnionOrIntersectionType) {
6115+
let sourceTypes = source.types;
6116+
let sourceIndex = 0;
6117+
let modified = false;
6118+
while (sourceIndex < sourceTypes.length) {
6119+
if (typeIdenticalToSomeType(sourceTypes[sourceIndex], target)) {
6120+
if (!modified) {
6121+
sourceTypes = sourceTypes.slice(0);
6122+
modified = true;
6123+
}
6124+
sourceTypes.splice(sourceIndex, 1);
6125+
}
6126+
else {
6127+
sourceIndex++;
6128+
}
6129+
}
6130+
if (modified) {
6131+
return source.flags & TypeFlags.Union ? getUnionType(sourceTypes, /*noSubtypeReduction*/ true) : getIntersectionType(sourceTypes);
6132+
}
6133+
return source;
6134+
}
6135+
60896136
function getInferenceCandidates(context: InferenceContext, index: number): Type[] {
60906137
let inferences = context.inferences[index];
60916138
return inferences.primary || inferences.secondary || emptyArray;
@@ -7859,7 +7906,6 @@ namespace ts {
78597906
return prop || unknownSymbol;
78607907
}
78617908

7862-
let jsxElementClassType: Type = undefined;
78637909
function getJsxGlobalElementClassType(): Type {
78647910
if (!jsxElementClassType) {
78657911
jsxElementClassType = getExportedTypeFromNamespace(JsxNames.JSX, JsxNames.ElementClass);
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

0 commit comments

Comments
 (0)