Skip to content

Commit d8080a0

Browse files
committed
Merge pull request #1035 from Microsoft/improvedTypeInference
Improved type inference (fixes #1011)
2 parents 2b70196 + 4c4484b commit d8080a0

File tree

5 files changed

+231
-17
lines changed

5 files changed

+231
-17
lines changed

src/compiler/checker.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4007,8 +4007,10 @@ module ts {
40074007
}
40084008

40094009
function createInferenceContext(typeParameters: TypeParameter[], inferUnionTypes: boolean): InferenceContext {
4010-
var inferences: Type[][] = [];
4011-
for (var i = 0; i < typeParameters.length; i++) inferences.push([]);
4010+
var inferences: TypeInferences[] = [];
4011+
for (var i = 0; i < typeParameters.length; i++) {
4012+
inferences.push({ primary: undefined, secondary: undefined });
4013+
}
40124014
return {
40134015
typeParameters: typeParameters,
40144016
inferUnionTypes: inferUnionTypes,
@@ -4022,6 +4024,7 @@ module ts {
40224024
var sourceStack: Type[];
40234025
var targetStack: Type[];
40244026
var depth = 0;
4027+
var inferiority = 0;
40254028
inferFromTypes(source, target);
40264029

40274030
function isInProcess(source: Type, target: Type) {
@@ -4050,9 +4053,11 @@ module ts {
40504053
var typeParameters = context.typeParameters;
40514054
for (var i = 0; i < typeParameters.length; i++) {
40524055
if (target === typeParameters[i]) {
4053-
context.inferenceCount++;
40544056
var inferences = context.inferences[i];
4055-
if (!contains(inferences, source)) inferences.push(source);
4057+
var candidates = inferiority ?
4058+
inferences.secondary || (inferences.secondary = []) :
4059+
inferences.primary || (inferences.primary = []);
4060+
if (!contains(candidates, source)) candidates.push(source);
40564061
break;
40574062
}
40584063
}
@@ -4067,7 +4072,6 @@ module ts {
40674072
}
40684073
else if (target.flags & TypeFlags.Union) {
40694074
var targetTypes = (<UnionType>target).types;
4070-
var startCount = context.inferenceCount;
40714075
var typeParameterCount = 0;
40724076
var typeParameter: TypeParameter;
40734077
// First infer to each type in union that isn't a type parameter
@@ -4081,9 +4085,11 @@ module ts {
40814085
inferFromTypes(source, t);
40824086
}
40834087
}
4084-
// If no inferences were produced above and union contains a single naked type parameter, infer to that type parameter
4085-
if (context.inferenceCount === startCount && typeParameterCount === 1) {
4088+
// If union contains a single naked type parameter, make a secondary inference to that type parameter
4089+
if (typeParameterCount === 1) {
4090+
inferiority++;
40864091
inferFromTypes(source, typeParameter);
4092+
inferiority--;
40874093
}
40884094
}
40894095
else if (source.flags & TypeFlags.Union) {
@@ -4153,10 +4159,15 @@ module ts {
41534159
}
41544160
}
41554161

4162+
function getInferenceCandidates(context: InferenceContext, index: number): Type[]{
4163+
var inferences = context.inferences[index];
4164+
return inferences.primary || inferences.secondary || emptyArray;
4165+
}
4166+
41564167
function getInferredType(context: InferenceContext, index: number): Type {
41574168
var inferredType = context.inferredTypes[index];
41584169
if (!inferredType) {
4159-
var inferences = context.inferences[index];
4170+
var inferences = getInferenceCandidates(context, index);
41604171
if (inferences.length) {
41614172
// Infer widened union or supertype, or the undefined type for no common supertype
41624173
var unionOrSuperType = context.inferUnionTypes ? getUnionType(inferences) : getCommonSupertype(inferences);
@@ -4166,7 +4177,6 @@ module ts {
41664177
// Infer the empty object type when no inferences were made
41674178
inferredType = emptyObjectType;
41684179
}
4169-
41704180
if (inferredType !== inferenceFailureType) {
41714181
var constraint = getConstraintOfTypeParameter(context.typeParameters[index]);
41724182
inferredType = constraint && !isTypeAssignableTo(inferredType, constraint) ? constraint : inferredType;
@@ -5404,7 +5414,7 @@ module ts {
54045414
else {
54055415
Debug.assert(resultOfFailedInference.failedTypeParameterIndex >= 0);
54065416
var failedTypeParameter = candidateForTypeArgumentError.typeParameters[resultOfFailedInference.failedTypeParameterIndex];
5407-
var inferenceCandidates = resultOfFailedInference.inferences[resultOfFailedInference.failedTypeParameterIndex];
5417+
var inferenceCandidates = getInferenceCandidates(resultOfFailedInference, resultOfFailedInference.failedTypeParameterIndex);
54085418

54095419
var diagnosticChainHead = chainDiagnosticMessages(/*details*/ undefined, // details will be provided by call to reportNoCommonSupertypeError
54105420
Diagnostics.The_type_argument_for_type_parameter_0_cannot_be_inferred_from_the_usage_Consider_specifying_the_type_arguments_explicitly,

src/compiler/types.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,14 +1041,18 @@ module ts {
10411041
(t: Type): Type;
10421042
}
10431043

1044+
export interface TypeInferences {
1045+
primary: Type[]; // Inferences made directly to a type parameter
1046+
secondary: Type[]; // Inferences made to a type parameter in a union type
1047+
}
1048+
10441049
export interface InferenceContext {
1045-
typeParameters: TypeParameter[]; // Type parameters for which inferences are made
1046-
inferUnionTypes: boolean; // Infer union types for disjoint candidates (otherwise undefinedType)
1047-
inferenceCount: number; // Incremented for every inference made (whether new or not)
1048-
inferences: Type[][]; // Inferences made for each type parameter
1049-
inferredTypes: Type[]; // Inferred type for each type parameter
1050-
failedTypeParameterIndex?: number; // Index of type parameter for which inference failed
1051-
// It is optional because in contextual signature instantiation, nothing fails
1050+
typeParameters: TypeParameter[]; // Type parameters for which inferences are made
1051+
inferUnionTypes: boolean; // Infer union types for disjoint candidates (otherwise undefinedType)
1052+
inferences: TypeInferences[]; // Inferences made for each type parameter
1053+
inferredTypes: Type[]; // Inferred type for each type parameter
1054+
failedTypeParameterIndex?: number; // Index of type parameter for which inference failed
1055+
// It is optional because in contextual signature instantiation, nothing fails
10521056
}
10531057

10541058
export interface DiagnosticMessage {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//// [unionTypeInference.ts]
2+
// Verify that inferences made *to* a type parameter in a union type are secondary
3+
// to inferences made directly to that type parameter
4+
5+
function f<T>(x: T, y: string|T): T {
6+
return x;
7+
}
8+
9+
var a1: number;
10+
var a1 = f(1, 2);
11+
var a2: number;
12+
var a2 = f(1, "hello");
13+
var a3: number;
14+
var a3 = f(1, a1 || "hello");
15+
var a4: any;
16+
var a4 = f(undefined, "abc");
17+
18+
function g<T>(value: [string, T]): T {
19+
return value[1];
20+
}
21+
22+
var b1: boolean;
23+
var b1 = g(["string", true]);
24+
25+
function h<T>(x: string|boolean|T): T {
26+
return typeof x === "string" || typeof x === "boolean" ? undefined : x;
27+
}
28+
29+
var c1: number;
30+
var c1 = h(5);
31+
var c2: string;
32+
var c2 = h("abc");
33+
34+
35+
//// [unionTypeInference.js]
36+
// Verify that inferences made *to* a type parameter in a union type are secondary
37+
// to inferences made directly to that type parameter
38+
function f(x, y) {
39+
return x;
40+
}
41+
var a1;
42+
var a1 = f(1, 2);
43+
var a2;
44+
var a2 = f(1, "hello");
45+
var a3;
46+
var a3 = f(1, a1 || "hello");
47+
var a4;
48+
var a4 = f(undefined, "abc");
49+
function g(value) {
50+
return value[1];
51+
}
52+
var b1;
53+
var b1 = g(["string", true]);
54+
function h(x) {
55+
return typeof x === "string" || typeof x === "boolean" ? undefined : x;
56+
}
57+
var c1;
58+
var c1 = h(5);
59+
var c2;
60+
var c2 = h("abc");
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
=== tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts ===
2+
// Verify that inferences made *to* a type parameter in a union type are secondary
3+
// to inferences made directly to that type parameter
4+
5+
function f<T>(x: T, y: string|T): T {
6+
>f : <T>(x: T, y: string | T) => T
7+
>T : T
8+
>x : T
9+
>T : T
10+
>y : string | T
11+
>T : T
12+
>T : T
13+
14+
return x;
15+
>x : T
16+
}
17+
18+
var a1: number;
19+
>a1 : number
20+
21+
var a1 = f(1, 2);
22+
>a1 : number
23+
>f(1, 2) : number
24+
>f : <T>(x: T, y: string | T) => T
25+
26+
var a2: number;
27+
>a2 : number
28+
29+
var a2 = f(1, "hello");
30+
>a2 : number
31+
>f(1, "hello") : number
32+
>f : <T>(x: T, y: string | T) => T
33+
34+
var a3: number;
35+
>a3 : number
36+
37+
var a3 = f(1, a1 || "hello");
38+
>a3 : number
39+
>f(1, a1 || "hello") : number
40+
>f : <T>(x: T, y: string | T) => T
41+
>a1 || "hello" : string | number
42+
>a1 : number
43+
44+
var a4: any;
45+
>a4 : any
46+
47+
var a4 = f(undefined, "abc");
48+
>a4 : any
49+
>f(undefined, "abc") : any
50+
>f : <T>(x: T, y: string | T) => T
51+
>undefined : undefined
52+
53+
function g<T>(value: [string, T]): T {
54+
>g : <T>(value: [string, T]) => T
55+
>T : T
56+
>value : [string, T]
57+
>T : T
58+
>T : T
59+
60+
return value[1];
61+
>value[1] : T
62+
>value : [string, T]
63+
}
64+
65+
var b1: boolean;
66+
>b1 : boolean
67+
68+
var b1 = g(["string", true]);
69+
>b1 : boolean
70+
>g(["string", true]) : boolean
71+
>g : <T>(value: [string, T]) => T
72+
>["string", true] : [string, boolean]
73+
74+
function h<T>(x: string|boolean|T): T {
75+
>h : <T>(x: string | boolean | T) => T
76+
>T : T
77+
>x : string | boolean | T
78+
>T : T
79+
>T : T
80+
81+
return typeof x === "string" || typeof x === "boolean" ? undefined : x;
82+
>typeof x === "string" || typeof x === "boolean" ? undefined : x : T
83+
>typeof x === "string" || typeof x === "boolean" : boolean
84+
>typeof x === "string" : boolean
85+
>typeof x : string
86+
>x : string | boolean | T
87+
>typeof x === "boolean" : boolean
88+
>typeof x : string
89+
>x : boolean | T
90+
>undefined : undefined
91+
>x : T
92+
}
93+
94+
var c1: number;
95+
>c1 : number
96+
97+
var c1 = h(5);
98+
>c1 : number
99+
>h(5) : number
100+
>h : <T>(x: string | boolean | T) => T
101+
102+
var c2: string;
103+
>c2 : string
104+
105+
var c2 = h("abc");
106+
>c2 : string
107+
>h("abc") : string
108+
>h : <T>(x: string | boolean | T) => T
109+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Verify that inferences made *to* a type parameter in a union type are secondary
2+
// to inferences made directly to that type parameter
3+
4+
function f<T>(x: T, y: string|T): T {
5+
return x;
6+
}
7+
8+
var a1: number;
9+
var a1 = f(1, 2);
10+
var a2: number;
11+
var a2 = f(1, "hello");
12+
var a3: number;
13+
var a3 = f(1, a1 || "hello");
14+
var a4: any;
15+
var a4 = f(undefined, "abc");
16+
17+
function g<T>(value: [string, T]): T {
18+
return value[1];
19+
}
20+
21+
var b1: boolean;
22+
var b1 = g(["string", true]);
23+
24+
function h<T>(x: string|boolean|T): T {
25+
return typeof x === "string" || typeof x === "boolean" ? undefined : x;
26+
}
27+
28+
var c1: number;
29+
var c1 = h(5);
30+
var c2: string;
31+
var c2 = h("abc");

0 commit comments

Comments
 (0)