Skip to content

Commit 3f3e974

Browse files
committed
Merge pull request #1861 from Microsoft/deeperTypeInference
Deeper type inference in context sensitive arguments
2 parents de46195 + 34ed45d commit 3f3e974

6 files changed

+178
-28
lines changed

src/compiler/checker.ts

+23-16
Original file line numberDiff line numberDiff line change
@@ -4340,6 +4340,9 @@ module ts {
43404340
}
43414341

43424342
function inferFromTypes(source: Type, target: Type) {
4343+
if (source === anyFunctionType) {
4344+
return;
4345+
}
43434346
if (target.flags & TypeFlags.TypeParameter) {
43444347
// If target is a type parameter, make an inference
43454348
var typeParameters = context.typeParameters;
@@ -5904,28 +5907,31 @@ module ts {
59045907
return getSignatureInstantiation(signature, getInferredTypes(context));
59055908
}
59065909

5907-
function inferTypeArguments(signature: Signature, args: Expression[], excludeArgument?: boolean[]): InferenceContext {
5910+
function inferTypeArguments(signature: Signature, args: Expression[], excludeArgument: boolean[]): InferenceContext {
59085911
var typeParameters = signature.typeParameters;
59095912
var context = createInferenceContext(typeParameters, /*inferUnionTypes*/ false);
5910-
var mapper = createInferenceMapper(context);
5911-
// First infer from arguments that are not context sensitive
5913+
var inferenceMapper = createInferenceMapper(context);
5914+
5915+
// We perform two passes over the arguments. In the first pass we infer from all arguments, but use
5916+
// wildcards for all context sensitive function expressions.
59125917
for (var i = 0; i < args.length; i++) {
59135918
if (args[i].kind === SyntaxKind.OmittedExpression) {
59145919
continue;
59155920
}
5916-
if (!excludeArgument || excludeArgument[i] === undefined) {
5917-
var parameterType = getTypeAtPosition(signature, i);
5918-
5919-
if (i === 0 && args[i].parent.kind === SyntaxKind.TaggedTemplateExpression) {
5920-
inferTypes(context, globalTemplateStringsArrayType, parameterType);
5921-
continue;
5922-
}
5923-
5924-
inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, mapper), parameterType);
5921+
var parameterType = getTypeAtPosition(signature, i);
5922+
if (i === 0 && args[i].parent.kind === SyntaxKind.TaggedTemplateExpression) {
5923+
inferTypes(context, globalTemplateStringsArrayType, parameterType);
5924+
continue;
59255925
}
5926+
// For context sensitive arguments we pass the identityMapper, which is a signal to treat all
5927+
// context sensitive function expressions as wildcards
5928+
var mapper = excludeArgument && excludeArgument[i] !== undefined ? identityMapper : inferenceMapper;
5929+
inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, mapper), parameterType);
59265930
}
59275931

5928-
// Next, infer from those context sensitive arguments that are no longer excluded
5932+
// In the second pass we visit only context sensitive arguments, and only those that aren't excluded, this
5933+
// time treating function expressions normally (which may cause previously inferred type arguments to be fixed
5934+
// as we construct types for contextually typed parameters)
59295935
if (excludeArgument) {
59305936
for (var i = 0; i < args.length; i++) {
59315937
if (args[i].kind === SyntaxKind.OmittedExpression) {
@@ -5934,10 +5940,11 @@ module ts {
59345940
// No need to special-case tagged templates; their excludeArgument value will be 'undefined'.
59355941
if (excludeArgument[i] === false) {
59365942
var parameterType = getTypeAtPosition(signature, i);
5937-
inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, mapper), parameterType);
5943+
inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, inferenceMapper), parameterType);
59385944
}
59395945
}
59405946
}
5947+
59415948
var inferredTypes = getInferredTypes(context);
59425949
// Inference has failed if the inferenceFailureType type is in list of inferences
59435950
context.failedTypeParameterIndex = indexOf(inferredTypes, inferenceFailureType);
@@ -6631,7 +6638,7 @@ module ts {
66316638
}
66326639

66336640
// The identityMapper object is used to indicate that function expressions are wildcards
6634-
if (contextualMapper === identityMapper) {
6641+
if (contextualMapper === identityMapper && isContextSensitive(node)) {
66356642
return anyFunctionType;
66366643
}
66376644
var links = getNodeLinks(node);
@@ -7305,7 +7312,7 @@ module ts {
73057312
case SyntaxKind.TypeAssertionExpression:
73067313
return checkTypeAssertion(<TypeAssertion>node);
73077314
case SyntaxKind.ParenthesizedExpression:
7308-
return checkExpression((<ParenthesizedExpression>node).expression);
7315+
return checkExpression((<ParenthesizedExpression>node).expression, contextualMapper);
73097316
case SyntaxKind.FunctionExpression:
73107317
case SyntaxKind.ArrowFunction:
73117318
return checkFunctionExpressionOrObjectLiteralMethod(<FunctionExpression>node, contextualMapper);
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts(2,22): error TS2339: Property 'foo' does not exist on type 'string'.
2-
tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts(3,10): error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
3-
Type argument candidate 'string' is not a valid type argument because it is not a supertype of candidate 'T'.
4-
tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts(3,32): error TS2339: Property 'foo' does not exist on type 'T'.
2+
tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts(3,32): error TS2339: Property 'foo' does not exist on type 'string'.
3+
tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts(3,38): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
54

65

76
==== tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts (3 errors) ====
@@ -10,8 +9,7 @@ tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts(3,32): error TS
109
~~~
1110
!!! error TS2339: Property 'foo' does not exist on type 'string'.
1211
var r9 = f10('', () => (a => a.foo), 1); // error
13-
~~~
14-
!!! error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
15-
!!! error TS2453: Type argument candidate 'string' is not a valid type argument because it is not a supertype of candidate 'T'.
1612
~~~
17-
!!! error TS2339: Property 'foo' does not exist on type 'T'.
13+
!!! error TS2339: Property 'foo' does not exist on type 'string'.
14+
~
15+
!!! error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

tests/baselines/reference/parenthesizedContexualTyping1.types

+5-5
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,11 @@ var l = fun(((Math.random() < 0.5 ? ((x => x)) : ((x => undefined)))), ((x => x)
220220
>x => undefined : (x: number) => any
221221
>x : number
222222
>undefined : undefined
223-
>((x => x)) : (x: number) => number
224-
>(x => x) : (x: number) => number
225-
>x => x : (x: number) => number
226-
>x : number
227-
>x : number
223+
>((x => x)) : (x: any) => any
224+
>(x => x) : (x: any) => any
225+
>x => x : (x: any) => any
226+
>x : any
227+
>x : any
228228

229229
var lambda1: (x: number) => number = x => x;
230230
>lambda1 : (x: number) => number
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
tests/cases/conformance/expressions/functionCalls/typeArgumentInferenceWithObjectLiteral.ts(35,10): error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
2+
Type argument candidate 'E1' is not a valid type argument because it is not a supertype of candidate 'E2'.
3+
4+
5+
==== tests/cases/conformance/expressions/functionCalls/typeArgumentInferenceWithObjectLiteral.ts (1 errors) ====
6+
interface Computed<T> {
7+
read(): T;
8+
write(value: T);
9+
}
10+
11+
function foo<T>(x: Computed<T>) { }
12+
13+
var s: string;
14+
15+
// Calls below should infer string for T and then assign that type to the value parameter
16+
foo({
17+
read: () => s,
18+
write: value => s = value
19+
});
20+
foo({
21+
write: value => s = value,
22+
read: () => s
23+
});
24+
25+
enum E1 { X }
26+
enum E2 { X }
27+
28+
// Check that we infer from both a.r and b before fixing T in a.w
29+
30+
declare function f1<T, U>(a: { w: (x: T) => U; r: () => T; }, b: T): U;
31+
32+
var v1: number;
33+
var v1 = f1({ w: x => x, r: () => 0 }, 0);
34+
var v1 = f1({ w: x => x, r: () => 0 }, E1.X);
35+
var v1 = f1({ w: x => x, r: () => E1.X }, 0);
36+
37+
var v2: E1;
38+
var v2 = f1({ w: x => x, r: () => E1.X }, E1.X);
39+
40+
var v3 = f1({ w: x => x, r: () => E1.X }, E2.X); // Error
41+
~~
42+
!!! error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
43+
!!! error TS2453: Type argument candidate 'E1' is not a valid type argument because it is not a supertype of candidate 'E2'.
44+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//// [typeArgumentInferenceWithObjectLiteral.ts]
2+
interface Computed<T> {
3+
read(): T;
4+
write(value: T);
5+
}
6+
7+
function foo<T>(x: Computed<T>) { }
8+
9+
var s: string;
10+
11+
// Calls below should infer string for T and then assign that type to the value parameter
12+
foo({
13+
read: () => s,
14+
write: value => s = value
15+
});
16+
foo({
17+
write: value => s = value,
18+
read: () => s
19+
});
20+
21+
enum E1 { X }
22+
enum E2 { X }
23+
24+
// Check that we infer from both a.r and b before fixing T in a.w
25+
26+
declare function f1<T, U>(a: { w: (x: T) => U; r: () => T; }, b: T): U;
27+
28+
var v1: number;
29+
var v1 = f1({ w: x => x, r: () => 0 }, 0);
30+
var v1 = f1({ w: x => x, r: () => 0 }, E1.X);
31+
var v1 = f1({ w: x => x, r: () => E1.X }, 0);
32+
33+
var v2: E1;
34+
var v2 = f1({ w: x => x, r: () => E1.X }, E1.X);
35+
36+
var v3 = f1({ w: x => x, r: () => E1.X }, E2.X); // Error
37+
38+
39+
//// [typeArgumentInferenceWithObjectLiteral.js]
40+
function foo(x) {
41+
}
42+
var s;
43+
// Calls below should infer string for T and then assign that type to the value parameter
44+
foo({
45+
read: function () { return s; },
46+
write: function (value) { return s = value; }
47+
});
48+
foo({
49+
write: function (value) { return s = value; },
50+
read: function () { return s; }
51+
});
52+
var E1;
53+
(function (E1) {
54+
E1[E1["X"] = 0] = "X";
55+
})(E1 || (E1 = {}));
56+
var E2;
57+
(function (E2) {
58+
E2[E2["X"] = 0] = "X";
59+
})(E2 || (E2 = {}));
60+
var v1;
61+
var v1 = f1({ w: function (x) { return x; }, r: function () { return 0; } }, 0);
62+
var v1 = f1({ w: function (x) { return x; }, r: function () { return 0; } }, 0 /* X */);
63+
var v1 = f1({ w: function (x) { return x; }, r: function () { return 0 /* X */; } }, 0);
64+
var v2;
65+
var v2 = f1({ w: function (x) { return x; }, r: function () { return 0 /* X */; } }, 0 /* X */);
66+
var v3 = f1({ w: function (x) { return x; }, r: function () { return 0 /* X */; } }, 0 /* X */); // Error
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
interface Computed<T> {
2+
read(): T;
3+
write(value: T);
4+
}
5+
6+
function foo<T>(x: Computed<T>) { }
7+
8+
var s: string;
9+
10+
// Calls below should infer string for T and then assign that type to the value parameter
11+
foo({
12+
read: () => s,
13+
write: value => s = value
14+
});
15+
foo({
16+
write: value => s = value,
17+
read: () => s
18+
});
19+
20+
enum E1 { X }
21+
enum E2 { X }
22+
23+
// Check that we infer from both a.r and b before fixing T in a.w
24+
25+
declare function f1<T, U>(a: { w: (x: T) => U; r: () => T; }, b: T): U;
26+
27+
var v1: number;
28+
var v1 = f1({ w: x => x, r: () => 0 }, 0);
29+
var v1 = f1({ w: x => x, r: () => 0 }, E1.X);
30+
var v1 = f1({ w: x => x, r: () => E1.X }, 0);
31+
32+
var v2: E1;
33+
var v2 = f1({ w: x => x, r: () => E1.X }, E1.X);
34+
35+
var v3 = f1({ w: x => x, r: () => E1.X }, E2.X); // Error

0 commit comments

Comments
 (0)