Skip to content

Commit 60f3b9b

Browse files
Merge pull request #1492 from Microsoft/contextualTemplateTyping
Fixed contextual type resolution and type checking for tagged template expressions.
2 parents ef71290 + 1f6cd94 commit 60f3b9b

20 files changed

+486
-7
lines changed

src/compiler/checker.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4813,17 +4813,25 @@ module ts {
48134813
return undefined;
48144814
}
48154815

4816-
// In a typed function call, an argument expression is contextually typed by the type of the corresponding parameter.
4817-
function getContextualTypeForArgument(node: Expression): Type {
4818-
var callExpression = <CallExpression>node.parent;
4819-
var argIndex = indexOf(callExpression.arguments, node);
4816+
// In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter.
4817+
function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type {
4818+
var args = getEffectiveCallArguments(callTarget);
4819+
var argIndex = indexOf(args, arg);
48204820
if (argIndex >= 0) {
4821-
var signature = getResolvedSignature(callExpression);
4821+
var signature = getResolvedSignature(callTarget);
48224822
return getTypeAtPosition(signature, argIndex);
48234823
}
48244824
return undefined;
48254825
}
48264826

4827+
function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) {
4828+
if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) {
4829+
return getContextualTypeForArgument(<TaggedTemplateExpression>template.parent, substitutionExpression);
4830+
}
4831+
4832+
return undefined;
4833+
}
4834+
48274835
function getContextualTypeForBinaryOperand(node: Expression): Type {
48284836
var binaryExpression = <BinaryExpression>node.parent;
48294837
var operator = binaryExpression.operator;
@@ -4959,7 +4967,7 @@ module ts {
49594967
return getContextualTypeForReturnExpression(node);
49604968
case SyntaxKind.CallExpression:
49614969
case SyntaxKind.NewExpression:
4962-
return getContextualTypeForArgument(node);
4970+
return getContextualTypeForArgument(<CallExpression>parent, node);
49634971
case SyntaxKind.TypeAssertionExpression:
49644972
return getTypeFromTypeNode((<TypeAssertion>parent).type);
49654973
case SyntaxKind.BinaryExpression:
@@ -4970,6 +4978,9 @@ module ts {
49704978
return getContextualTypeForElementExpression(node);
49714979
case SyntaxKind.ConditionalExpression:
49724980
return getContextualTypeForConditionalOperand(node);
4981+
case SyntaxKind.TemplateSpan:
4982+
Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression);
4983+
return getContextualTypeForSubstitutionExpression(<TemplateExpression>parent.parent, node);
49734984
}
49744985
return undefined;
49754986
}
@@ -5571,7 +5582,7 @@ module ts {
55715582
}
55725583

55735584
/**
5574-
* Returns the effective arguments for an expression that works like a function invokation.
5585+
* Returns the effective arguments for an expression that works like a function invocation.
55755586
*
55765587
* If 'node' is a CallExpression or a NewExpression, then its argument list is returned.
55775588
* If 'node' is a TaggedTemplateExpression, a new argument list is constructed from the substitution
@@ -8636,6 +8647,8 @@ module ts {
86368647
case SyntaxKind.CallExpression:
86378648
case SyntaxKind.NewExpression:
86388649
case SyntaxKind.TaggedTemplateExpression:
8650+
case SyntaxKind.TemplateExpression:
8651+
case SyntaxKind.TemplateSpan:
86398652
case SyntaxKind.TypeAssertionExpression:
86408653
case SyntaxKind.ParenthesizedExpression:
86418654
case SyntaxKind.TypeOfExpression:
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//// [taggedTemplateContextualTyping1.ts]
2+
3+
type FuncType = (x: <T>(p: T) => T) => typeof x;
4+
5+
function tempTag1<T>(templateStrs: TemplateStringsArray, f: FuncType, x: T): T;
6+
function tempTag1<T>(templateStrs: TemplateStringsArray, f: FuncType, h: FuncType, x: T): T;
7+
function tempTag1<T>(...rest: any[]): T {
8+
return undefined;
9+
}
10+
11+
// If contextual typing takes place, these functions should work.
12+
// Otherwise, the arrow functions' parameters will be typed as 'any',
13+
// and it is an error to invoke an any-typed value with type arguments,
14+
// so this test will error.
15+
tempTag1 `${ x => { x<number>(undefined); return x; } }${ y => { y<number>(undefined); return y; } }${ 10 }`;
16+
tempTag1 `${ x => { x<number>(undefined); return x; } }${ (y: <T>(p: T) => T) => { y<number>(undefined); return y } }${ undefined }`;
17+
tempTag1 `${ (x: <T>(p: T) => T) => { x<number>(undefined); return x; } }${ y => { y<number>(undefined); return y; } }${ undefined }`;
18+
19+
20+
//// [taggedTemplateContextualTyping1.js]
21+
function tempTag1() {
22+
var rest = [];
23+
for (var _i = 0; _i < arguments.length; _i++) {
24+
rest[_i - 0] = arguments[_i];
25+
}
26+
return undefined;
27+
}
28+
// If contextual typing takes place, these functions should work.
29+
// Otherwise, the arrow functions' parameters will be typed as 'any',
30+
// and it is an error to invoke an any-typed value with type arguments,
31+
// so this test will error.
32+
tempTag1 `${function (x) {
33+
x(undefined);
34+
return x;
35+
}}${function (y) {
36+
y(undefined);
37+
return y;
38+
}}${10}`;
39+
tempTag1 `${function (x) {
40+
x(undefined);
41+
return x;
42+
}}${function (y) {
43+
y(undefined);
44+
return y;
45+
}}${undefined}`;
46+
tempTag1 `${function (x) {
47+
x(undefined);
48+
return x;
49+
}}${function (y) {
50+
y(undefined);
51+
return y;
52+
}}${undefined}`;
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
=== tests/cases/conformance/expressions/contextualTyping/taggedTemplateContextualTyping1.ts ===
2+
3+
type FuncType = (x: <T>(p: T) => T) => typeof x;
4+
>FuncType : (x: <T>(p: T) => T) => <T>(p: T) => T
5+
>x : <T>(p: T) => T
6+
>T : T
7+
>p : T
8+
>T : T
9+
>T : T
10+
>x : <T>(p: T) => T
11+
12+
function tempTag1<T>(templateStrs: TemplateStringsArray, f: FuncType, x: T): T;
13+
>tempTag1 : { <T>(templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, x: T): T; <T>(templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, h: (x: <T>(p: T) => T) => <T>(p: T) => T, x: T): T; }
14+
>T : T
15+
>templateStrs : TemplateStringsArray
16+
>TemplateStringsArray : TemplateStringsArray
17+
>f : (x: <T>(p: T) => T) => <T>(p: T) => T
18+
>FuncType : (x: <T>(p: T) => T) => <T>(p: T) => T
19+
>x : T
20+
>T : T
21+
>T : T
22+
23+
function tempTag1<T>(templateStrs: TemplateStringsArray, f: FuncType, h: FuncType, x: T): T;
24+
>tempTag1 : { <T>(templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, x: T): T; <T>(templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, h: (x: <T>(p: T) => T) => <T>(p: T) => T, x: T): T; }
25+
>T : T
26+
>templateStrs : TemplateStringsArray
27+
>TemplateStringsArray : TemplateStringsArray
28+
>f : (x: <T>(p: T) => T) => <T>(p: T) => T
29+
>FuncType : (x: <T>(p: T) => T) => <T>(p: T) => T
30+
>h : (x: <T>(p: T) => T) => <T>(p: T) => T
31+
>FuncType : (x: <T>(p: T) => T) => <T>(p: T) => T
32+
>x : T
33+
>T : T
34+
>T : T
35+
36+
function tempTag1<T>(...rest: any[]): T {
37+
>tempTag1 : { <T>(templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, x: T): T; <T>(templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, h: (x: <T>(p: T) => T) => <T>(p: T) => T, x: T): T; }
38+
>T : T
39+
>rest : any[]
40+
>T : T
41+
42+
return undefined;
43+
>undefined : undefined
44+
}
45+
46+
// If contextual typing takes place, these functions should work.
47+
// Otherwise, the arrow functions' parameters will be typed as 'any',
48+
// and it is an error to invoke an any-typed value with type arguments,
49+
// so this test will error.
50+
tempTag1 `${ x => { x<number>(undefined); return x; } }${ y => { y<number>(undefined); return y; } }${ 10 }`;
51+
>tempTag1 : { <T>(templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, x: T): T; <T>(templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, h: (x: <T>(p: T) => T) => <T>(p: T) => T, x: T): T; }
52+
>x => { x<number>(undefined); return x; } : (x: <T>(p: T) => T) => <T>(p: T) => T
53+
>x : <T>(p: T) => T
54+
>x<number>(undefined) : number
55+
>x : <T>(p: T) => T
56+
>undefined : undefined
57+
>x : <T>(p: T) => T
58+
>y => { y<number>(undefined); return y; } : (y: <T>(p: T) => T) => <T>(p: T) => T
59+
>y : <T>(p: T) => T
60+
>y<number>(undefined) : number
61+
>y : <T>(p: T) => T
62+
>undefined : undefined
63+
>y : <T>(p: T) => T
64+
65+
tempTag1 `${ x => { x<number>(undefined); return x; } }${ (y: <T>(p: T) => T) => { y<number>(undefined); return y } }${ undefined }`;
66+
>tempTag1 : { <T>(templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, x: T): T; <T>(templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, h: (x: <T>(p: T) => T) => <T>(p: T) => T, x: T): T; }
67+
>x => { x<number>(undefined); return x; } : (x: <T>(p: T) => T) => <T>(p: T) => T
68+
>x : <T>(p: T) => T
69+
>x<number>(undefined) : number
70+
>x : <T>(p: T) => T
71+
>undefined : undefined
72+
>x : <T>(p: T) => T
73+
>(y: <T>(p: T) => T) => { y<number>(undefined); return y } : (y: <T>(p: T) => T) => <T>(p: T) => T
74+
>y : <T>(p: T) => T
75+
>T : T
76+
>p : T
77+
>T : T
78+
>T : T
79+
>y<number>(undefined) : number
80+
>y : <T>(p: T) => T
81+
>undefined : undefined
82+
>y : <T>(p: T) => T
83+
>undefined : undefined
84+
85+
tempTag1 `${ (x: <T>(p: T) => T) => { x<number>(undefined); return x; } }${ y => { y<number>(undefined); return y; } }${ undefined }`;
86+
>tempTag1 : { <T>(templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, x: T): T; <T>(templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, h: (x: <T>(p: T) => T) => <T>(p: T) => T, x: T): T; }
87+
>(x: <T>(p: T) => T) => { x<number>(undefined); return x; } : (x: <T>(p: T) => T) => <T>(p: T) => T
88+
>x : <T>(p: T) => T
89+
>T : T
90+
>p : T
91+
>T : T
92+
>T : T
93+
>x<number>(undefined) : number
94+
>x : <T>(p: T) => T
95+
>undefined : undefined
96+
>x : <T>(p: T) => T
97+
>y => { y<number>(undefined); return y; } : (y: <T>(p: T) => T) => <T>(p: T) => T
98+
>y : <T>(p: T) => T
99+
>y<number>(undefined) : number
100+
>y : <T>(p: T) => T
101+
>undefined : undefined
102+
>y : <T>(p: T) => T
103+
>undefined : undefined
104+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//// [taggedTemplateContextualTyping2.ts]
2+
3+
type FuncType1 = (x: <T>(p: T) => T) => typeof x;
4+
type FuncType2 = (x: <S, T>(p: T) => T) => typeof x;
5+
6+
function tempTag2(templateStrs: TemplateStringsArray, f: FuncType1, x: number): number;
7+
function tempTag2(templateStrs: TemplateStringsArray, f: FuncType2, h: FuncType2, x: string): string;
8+
function tempTag2(...rest: any[]): any {
9+
return undefined;
10+
}
11+
12+
// If contextual typing takes place, these functions should work.
13+
// Otherwise, the arrow functions' parameters will be typed as 'any',
14+
// and it is an error to invoke an any-typed value with type arguments,
15+
// so this test will error.
16+
tempTag2 `${ x => { x<number>(undefined); return x; } }${ 0 }`;
17+
tempTag2 `${ x => { x<number, string>(undefined); return x; } }${ y => { y<string, number>(null); return y; } }${ "hello" }`;
18+
tempTag2 `${ x => { x<number, string>(undefined); return x; } }${ undefined }${ "hello" }`;
19+
20+
//// [taggedTemplateContextualTyping2.js]
21+
function tempTag2() {
22+
var rest = [];
23+
for (var _i = 0; _i < arguments.length; _i++) {
24+
rest[_i - 0] = arguments[_i];
25+
}
26+
return undefined;
27+
}
28+
// If contextual typing takes place, these functions should work.
29+
// Otherwise, the arrow functions' parameters will be typed as 'any',
30+
// and it is an error to invoke an any-typed value with type arguments,
31+
// so this test will error.
32+
tempTag2 `${function (x) {
33+
x(undefined);
34+
return x;
35+
}}${0}`;
36+
tempTag2 `${function (x) {
37+
x(undefined);
38+
return x;
39+
}}${function (y) {
40+
y(null);
41+
return y;
42+
}}${"hello"}`;
43+
tempTag2 `${function (x) {
44+
x(undefined);
45+
return x;
46+
}}${undefined}${"hello"}`;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
=== tests/cases/conformance/expressions/contextualTyping/taggedTemplateContextualTyping2.ts ===
2+
3+
type FuncType1 = (x: <T>(p: T) => T) => typeof x;
4+
>FuncType1 : (x: <T>(p: T) => T) => <T>(p: T) => T
5+
>x : <T>(p: T) => T
6+
>T : T
7+
>p : T
8+
>T : T
9+
>T : T
10+
>x : <T>(p: T) => T
11+
12+
type FuncType2 = (x: <S, T>(p: T) => T) => typeof x;
13+
>FuncType2 : (x: <S, T>(p: T) => T) => <S, T>(p: T) => T
14+
>x : <S, T>(p: T) => T
15+
>S : S
16+
>T : T
17+
>p : T
18+
>T : T
19+
>T : T
20+
>x : <S, T>(p: T) => T
21+
22+
function tempTag2(templateStrs: TemplateStringsArray, f: FuncType1, x: number): number;
23+
>tempTag2 : { (templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, x: number): number; (templateStrs: TemplateStringsArray, f: (x: <S, T>(p: T) => T) => <S, T>(p: T) => T, h: (x: <S, T>(p: T) => T) => <S, T>(p: T) => T, x: string): string; }
24+
>templateStrs : TemplateStringsArray
25+
>TemplateStringsArray : TemplateStringsArray
26+
>f : (x: <T>(p: T) => T) => <T>(p: T) => T
27+
>FuncType1 : (x: <T>(p: T) => T) => <T>(p: T) => T
28+
>x : number
29+
30+
function tempTag2(templateStrs: TemplateStringsArray, f: FuncType2, h: FuncType2, x: string): string;
31+
>tempTag2 : { (templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, x: number): number; (templateStrs: TemplateStringsArray, f: (x: <S, T>(p: T) => T) => <S, T>(p: T) => T, h: (x: <S, T>(p: T) => T) => <S, T>(p: T) => T, x: string): string; }
32+
>templateStrs : TemplateStringsArray
33+
>TemplateStringsArray : TemplateStringsArray
34+
>f : (x: <S, T>(p: T) => T) => <S, T>(p: T) => T
35+
>FuncType2 : (x: <S, T>(p: T) => T) => <S, T>(p: T) => T
36+
>h : (x: <S, T>(p: T) => T) => <S, T>(p: T) => T
37+
>FuncType2 : (x: <S, T>(p: T) => T) => <S, T>(p: T) => T
38+
>x : string
39+
40+
function tempTag2(...rest: any[]): any {
41+
>tempTag2 : { (templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, x: number): number; (templateStrs: TemplateStringsArray, f: (x: <S, T>(p: T) => T) => <S, T>(p: T) => T, h: (x: <S, T>(p: T) => T) => <S, T>(p: T) => T, x: string): string; }
42+
>rest : any[]
43+
44+
return undefined;
45+
>undefined : undefined
46+
}
47+
48+
// If contextual typing takes place, these functions should work.
49+
// Otherwise, the arrow functions' parameters will be typed as 'any',
50+
// and it is an error to invoke an any-typed value with type arguments,
51+
// so this test will error.
52+
tempTag2 `${ x => { x<number>(undefined); return x; } }${ 0 }`;
53+
>tempTag2 : { (templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, x: number): number; (templateStrs: TemplateStringsArray, f: (x: <S, T>(p: T) => T) => <S, T>(p: T) => T, h: (x: <S, T>(p: T) => T) => <S, T>(p: T) => T, x: string): string; }
54+
>x => { x<number>(undefined); return x; } : (x: <T>(p: T) => T) => <T>(p: T) => T
55+
>x : <T>(p: T) => T
56+
>x<number>(undefined) : number
57+
>x : <T>(p: T) => T
58+
>undefined : undefined
59+
>x : <T>(p: T) => T
60+
61+
tempTag2 `${ x => { x<number, string>(undefined); return x; } }${ y => { y<string, number>(null); return y; } }${ "hello" }`;
62+
>tempTag2 : { (templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, x: number): number; (templateStrs: TemplateStringsArray, f: (x: <S, T>(p: T) => T) => <S, T>(p: T) => T, h: (x: <S, T>(p: T) => T) => <S, T>(p: T) => T, x: string): string; }
63+
>x => { x<number, string>(undefined); return x; } : (x: <S, T>(p: T) => T) => <S, T>(p: T) => T
64+
>x : <S, T>(p: T) => T
65+
>x<number, string>(undefined) : string
66+
>x : <S, T>(p: T) => T
67+
>undefined : undefined
68+
>x : <S, T>(p: T) => T
69+
>y => { y<string, number>(null); return y; } : (y: <S, T>(p: T) => T) => <S, T>(p: T) => T
70+
>y : <S, T>(p: T) => T
71+
>y<string, number>(null) : number
72+
>y : <S, T>(p: T) => T
73+
>y : <S, T>(p: T) => T
74+
75+
tempTag2 `${ x => { x<number, string>(undefined); return x; } }${ undefined }${ "hello" }`;
76+
>tempTag2 : { (templateStrs: TemplateStringsArray, f: (x: <T>(p: T) => T) => <T>(p: T) => T, x: number): number; (templateStrs: TemplateStringsArray, f: (x: <S, T>(p: T) => T) => <S, T>(p: T) => T, h: (x: <S, T>(p: T) => T) => <S, T>(p: T) => T, x: string): string; }
77+
>x => { x<number, string>(undefined); return x; } : (x: <S, T>(p: T) => T) => <S, T>(p: T) => T
78+
>x : <S, T>(p: T) => T
79+
>x<number, string>(undefined) : string
80+
>x : <S, T>(p: T) => T
81+
>undefined : undefined
82+
>x : <S, T>(p: T) => T
83+
>undefined : undefined
84+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
tests/cases/conformance/es6/templates/taggedTemplateStringsWithTypeErrorInFunctionExpressionsInSubstitutionExpression.ts(6,5): error TS1159: Tagged templates are only available when targeting ECMAScript 6 and higher.
2+
tests/cases/conformance/es6/templates/taggedTemplateStringsWithTypeErrorInFunctionExpressionsInSubstitutionExpression.ts(6,31): error TS2322: Type 'string' is not assignable to type 'number'.
3+
4+
5+
==== tests/cases/conformance/es6/templates/taggedTemplateStringsWithTypeErrorInFunctionExpressionsInSubstitutionExpression.ts (2 errors) ====
6+
7+
8+
function foo(...rest: any[]) {
9+
}
10+
11+
foo `${function (x: number) { x = "bad"; } }`;
12+
~~~
13+
!!! error TS1159: Tagged templates are only available when targeting ECMAScript 6 and higher.
14+
~
15+
!!! error TS2322: Type 'string' is not assignable to type 'number'.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
tests/cases/conformance/es6/templates/taggedTemplateStringsWithTypeErrorInFunctionExpressionsInSubstitutionExpressionES6.ts(5,31): error TS2322: Type 'string' is not assignable to type 'number'.
2+
3+
4+
==== tests/cases/conformance/es6/templates/taggedTemplateStringsWithTypeErrorInFunctionExpressionsInSubstitutionExpressionES6.ts (1 errors) ====
5+
6+
function foo(...rest: any[]) {
7+
}
8+
9+
foo `${function (x: number) { x = "bad"; } }`;
10+
~
11+
!!! error TS2322: Type 'string' is not assignable to type 'number'.

0 commit comments

Comments
 (0)