From b65a422c7a53528916eea9b43cb57e7ff91e886b Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 15 Dec 2014 14:11:21 -0800 Subject: [PATCH] Fixed contextual typing for tagged template expressions. --- src/compiler/checker.ts | 27 +++- .../taggedTemplateContextualTyping.js | 45 +++++++ .../taggedTemplateContextualTyping.types | 124 ++++++++++++++++++ .../taggedTemplateContextualTyping.ts | 22 ++++ ...TypedFunctionInTaggedTemplateExpression.ts | 44 +++++++ 5 files changed, 255 insertions(+), 7 deletions(-) create mode 100644 tests/baselines/reference/taggedTemplateContextualTyping.js create mode 100644 tests/baselines/reference/taggedTemplateContextualTyping.types create mode 100644 tests/cases/conformance/expressions/contextualTyping/taggedTemplateContextualTyping.ts create mode 100644 tests/cases/fourslash/quickInfoForContextuallyTypedFunctionInTaggedTemplateExpression.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 889b387d4f4..502b87c0c9d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4813,17 +4813,25 @@ module ts { return undefined; } - // In a typed function call, an argument expression is contextually typed by the type of the corresponding parameter. - function getContextualTypeForArgument(node: Expression): Type { - var callExpression = node.parent; - var argIndex = indexOf(callExpression.arguments, node); + // In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter. + function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type { + var args = getEffectiveCallArguments(callTarget); + var argIndex = indexOf(args, arg); if (argIndex >= 0) { - var signature = getResolvedSignature(callExpression); + var signature = getResolvedSignature(callTarget); return getTypeAtPosition(signature, argIndex); } return undefined; } + function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) { + if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) { + return getContextualTypeForArgument(template.parent, substitutionExpression); + } + + return undefined; + } + function getContextualTypeForBinaryOperand(node: Expression): Type { var binaryExpression = node.parent; var operator = binaryExpression.operator; @@ -4959,7 +4967,7 @@ module ts { return getContextualTypeForReturnExpression(node); case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: - return getContextualTypeForArgument(node); + return getContextualTypeForArgument(parent, node); case SyntaxKind.TypeAssertionExpression: return getTypeFromTypeNode((parent).type); case SyntaxKind.BinaryExpression: @@ -4970,6 +4978,11 @@ module ts { return getContextualTypeForElementExpression(node); case SyntaxKind.ConditionalExpression: return getContextualTypeForConditionalOperand(node); + case SyntaxKind.TemplateExpression: + return getContextualTypeForSubstitutionExpression(parent.parent, node); + case SyntaxKind.TemplateSpan: + Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); + return getContextualTypeForSubstitutionExpression(parent.parent, node); } return undefined; } @@ -5571,7 +5584,7 @@ module ts { } /** - * Returns the effective arguments for an expression that works like a function invokation. + * Returns the effective arguments for an expression that works like a function invocation. * * If 'node' is a CallExpression or a NewExpression, then its argument list is returned. * If 'node' is a TaggedTemplateExpression, a new argument list is constructed from the substitution diff --git a/tests/baselines/reference/taggedTemplateContextualTyping.js b/tests/baselines/reference/taggedTemplateContextualTyping.js new file mode 100644 index 00000000000..64b43031c76 --- /dev/null +++ b/tests/baselines/reference/taggedTemplateContextualTyping.js @@ -0,0 +1,45 @@ +//// [taggedTemplateContextualTyping.ts] + +function tempTag1(templateStrs: TemplateStringsArray, f: (x: T) => T, x: T): T; +function tempTag1(templateStrs: TemplateStringsArray, f: (x: T) => T, h: (y: T) => T, x: T): T; +function tempTag1(...rest: any[]): T { + return undefined; +} + +tempTag1 `${ x => x }${ 10 }`; +tempTag1 `${ x => x }${ y => y }${ 10 }`; +tempTag1 `${ x => x }${ (y: number) => y }${ undefined }`; +tempTag1 `${ (x: number) => x }${ y => y }${ undefined }`; + +function tempTag2(templateStrs: TemplateStringsArray, f: (x: number) => number, x: number): number; +function tempTag2(templateStrs: TemplateStringsArray, f: (x: string) => string, h: (y: string) => string, x: string): string; +function tempTag2(...rest: any[]): any { + return undefined; +} + +tempTag2 `${ x => x }${ 0 }`; +tempTag2 `${ x => x }${ y => y }${ "hello" }`; +tempTag2 `${ x => x }${ 0 }`; + +//// [taggedTemplateContextualTyping.js] +function tempTag1() { + var rest = []; + for (var _i = 0; _i < arguments.length; _i++) { + rest[_i - 0] = arguments[_i]; + } + return undefined; +} +tempTag1 `${function (x) { return x; }}${10}`; +tempTag1 `${function (x) { return x; }}${function (y) { return y; }}${10}`; +tempTag1 `${function (x) { return x; }}${function (y) { return y; }}${undefined}`; +tempTag1 `${function (x) { return x; }}${function (y) { return y; }}${undefined}`; +function tempTag2() { + var rest = []; + for (var _i = 0; _i < arguments.length; _i++) { + rest[_i - 0] = arguments[_i]; + } + return undefined; +} +tempTag2 `${function (x) { return x; }}${0}`; +tempTag2 `${function (x) { return x; }}${function (y) { return y; }}${"hello"}`; +tempTag2 `${function (x) { return x; }}${0}`; diff --git a/tests/baselines/reference/taggedTemplateContextualTyping.types b/tests/baselines/reference/taggedTemplateContextualTyping.types new file mode 100644 index 00000000000..d8d050e4643 --- /dev/null +++ b/tests/baselines/reference/taggedTemplateContextualTyping.types @@ -0,0 +1,124 @@ +=== tests/cases/conformance/expressions/contextualTyping/taggedTemplateContextualTyping.ts === + +function tempTag1(templateStrs: TemplateStringsArray, f: (x: T) => T, x: T): T; +>tempTag1 : { (templateStrs: TemplateStringsArray, f: (x: T) => T, x: T): T; (templateStrs: TemplateStringsArray, f: (x: T) => T, h: (y: T) => T, x: T): T; } +>T : T +>templateStrs : TemplateStringsArray +>TemplateStringsArray : TemplateStringsArray +>f : (x: T) => T +>x : T +>T : T +>T : T +>x : T +>T : T +>T : T + +function tempTag1(templateStrs: TemplateStringsArray, f: (x: T) => T, h: (y: T) => T, x: T): T; +>tempTag1 : { (templateStrs: TemplateStringsArray, f: (x: T) => T, x: T): T; (templateStrs: TemplateStringsArray, f: (x: T) => T, h: (y: T) => T, x: T): T; } +>T : T +>templateStrs : TemplateStringsArray +>TemplateStringsArray : TemplateStringsArray +>f : (x: T) => T +>x : T +>T : T +>T : T +>h : (y: T) => T +>y : T +>T : T +>T : T +>x : T +>T : T +>T : T + +function tempTag1(...rest: any[]): T { +>tempTag1 : { (templateStrs: TemplateStringsArray, f: (x: T) => T, x: T): T; (templateStrs: TemplateStringsArray, f: (x: T) => T, h: (y: T) => T, x: T): T; } +>T : T +>rest : any[] +>T : T + + return undefined; +>undefined : undefined +} + +tempTag1 `${ x => x }${ 10 }`; +>tempTag1 : { (templateStrs: TemplateStringsArray, f: (x: T) => T, x: T): T; (templateStrs: TemplateStringsArray, f: (x: T) => T, h: (y: T) => T, x: T): T; } +>x => x : (x: number) => number +>x : number +>x : number + +tempTag1 `${ x => x }${ y => y }${ 10 }`; +>tempTag1 : { (templateStrs: TemplateStringsArray, f: (x: T) => T, x: T): T; (templateStrs: TemplateStringsArray, f: (x: T) => T, h: (y: T) => T, x: T): T; } +>x => x : (x: number) => number +>x : number +>x : number +>y => y : (y: number) => number +>y : number +>y : number + +tempTag1 `${ x => x }${ (y: number) => y }${ undefined }`; +>tempTag1 : { (templateStrs: TemplateStringsArray, f: (x: T) => T, x: T): T; (templateStrs: TemplateStringsArray, f: (x: T) => T, h: (y: T) => T, x: T): T; } +>x => x : (x: number) => number +>x : number +>x : number +>(y: number) => y : (y: number) => number +>y : number +>y : number +>undefined : undefined + +tempTag1 `${ (x: number) => x }${ y => y }${ undefined }`; +>tempTag1 : { (templateStrs: TemplateStringsArray, f: (x: T) => T, x: T): T; (templateStrs: TemplateStringsArray, f: (x: T) => T, h: (y: T) => T, x: T): T; } +>(x: number) => x : (x: number) => number +>x : number +>x : number +>y => y : (y: number) => number +>y : number +>y : number +>undefined : undefined + +function tempTag2(templateStrs: TemplateStringsArray, f: (x: number) => number, x: number): number; +>tempTag2 : { (templateStrs: TemplateStringsArray, f: (x: number) => number, x: number): number; (templateStrs: TemplateStringsArray, f: (x: string) => string, h: (y: string) => string, x: string): string; } +>templateStrs : TemplateStringsArray +>TemplateStringsArray : TemplateStringsArray +>f : (x: number) => number +>x : number +>x : number + +function tempTag2(templateStrs: TemplateStringsArray, f: (x: string) => string, h: (y: string) => string, x: string): string; +>tempTag2 : { (templateStrs: TemplateStringsArray, f: (x: number) => number, x: number): number; (templateStrs: TemplateStringsArray, f: (x: string) => string, h: (y: string) => string, x: string): string; } +>templateStrs : TemplateStringsArray +>TemplateStringsArray : TemplateStringsArray +>f : (x: string) => string +>x : string +>h : (y: string) => string +>y : string +>x : string + +function tempTag2(...rest: any[]): any { +>tempTag2 : { (templateStrs: TemplateStringsArray, f: (x: number) => number, x: number): number; (templateStrs: TemplateStringsArray, f: (x: string) => string, h: (y: string) => string, x: string): string; } +>rest : any[] + + return undefined; +>undefined : undefined +} + +tempTag2 `${ x => x }${ 0 }`; +>tempTag2 : { (templateStrs: TemplateStringsArray, f: (x: number) => number, x: number): number; (templateStrs: TemplateStringsArray, f: (x: string) => string, h: (y: string) => string, x: string): string; } +>x => x : (x: number) => number +>x : number +>x : number + +tempTag2 `${ x => x }${ y => y }${ "hello" }`; +>tempTag2 : { (templateStrs: TemplateStringsArray, f: (x: number) => number, x: number): number; (templateStrs: TemplateStringsArray, f: (x: string) => string, h: (y: string) => string, x: string): string; } +>x => x : (x: string) => string +>x : string +>x : string +>y => y : (y: string) => string +>y : string +>y : string + +tempTag2 `${ x => x }${ 0 }`; +>tempTag2 : { (templateStrs: TemplateStringsArray, f: (x: number) => number, x: number): number; (templateStrs: TemplateStringsArray, f: (x: string) => string, h: (y: string) => string, x: string): string; } +>x => x : (x: number) => number +>x : number +>x : number + diff --git a/tests/cases/conformance/expressions/contextualTyping/taggedTemplateContextualTyping.ts b/tests/cases/conformance/expressions/contextualTyping/taggedTemplateContextualTyping.ts new file mode 100644 index 00000000000..5d6137a13ac --- /dev/null +++ b/tests/cases/conformance/expressions/contextualTyping/taggedTemplateContextualTyping.ts @@ -0,0 +1,22 @@ +// @target: ES6 + +function tempTag1(templateStrs: TemplateStringsArray, f: (x: T) => T, x: T): T; +function tempTag1(templateStrs: TemplateStringsArray, f: (x: T) => T, h: (y: T) => T, x: T): T; +function tempTag1(...rest: any[]): T { + return undefined; +} + +tempTag1 `${ x => x }${ 10 }`; +tempTag1 `${ x => x }${ y => y }${ 10 }`; +tempTag1 `${ x => x }${ (y: number) => y }${ undefined }`; +tempTag1 `${ (x: number) => x }${ y => y }${ undefined }`; + +function tempTag2(templateStrs: TemplateStringsArray, f: (x: number) => number, x: number): number; +function tempTag2(templateStrs: TemplateStringsArray, f: (x: string) => string, h: (y: string) => string, x: string): string; +function tempTag2(...rest: any[]): any { + return undefined; +} + +tempTag2 `${ x => x }${ 0 }`; +tempTag2 `${ x => x }${ y => y }${ "hello" }`; +tempTag2 `${ x => x }${ 0 }`; \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoForContextuallyTypedFunctionInTaggedTemplateExpression.ts b/tests/cases/fourslash/quickInfoForContextuallyTypedFunctionInTaggedTemplateExpression.ts new file mode 100644 index 00000000000..6d30d59ebd9 --- /dev/null +++ b/tests/cases/fourslash/quickInfoForContextuallyTypedFunctionInTaggedTemplateExpression.ts @@ -0,0 +1,44 @@ +/// + +////function tempTag1(templateStrs: TemplateStringsArray, f: (x: T) => T, x: T): T; +////function tempTag1(templateStrs: TemplateStringsArray, f: (x: T) => T, h: (y: T) => T, x: T): T; +////function tempTag1(...rest: any[]): T { +//// return undefined; +////} +//// +////tempTag1 `${ x => /*0*/x }${ 10 }`; +////tempTag1 `${ x => /*1*/x }${ x => /*2*/x }${ 10 }`; +////tempTag1 `${ x => /*3*/x }${ (x: number) => /*4*/x }${ undefined }`; +////tempTag1 `${ (x: number) => /*5*/x }${ x => /*6*/x }${ undefined }`; +//// +////function tempTag2(templateStrs: TemplateStringsArray, f: (x: number) => number, x: number): number; +////function tempTag2(templateStrs: TemplateStringsArray, f: (x: string) => string, h: (y: string) => string, x: string): string; +////function tempTag2(...rest: any[]): any { +//// return undefined; +////} +//// +////tempTag2 `${ x => /*7*/x }${ 0 }`; +////tempTag2 `${ x => /*8*/x }${ undefined }`; +////tempTag2 `${ x => /*9*/x }${ x => /*10*/x }${ "hello" }`; +////tempTag2 `${ x => /*11*/x }${ undefined }${ "hello" }`; + +// The first group of parameters, [0, 8], should all be contextually typed as 'number'. +// The second group, [9, 11], should be typed as 'string'. +var numTypedVariableCount = 9; +var strTypedVariableCount = 3; + +var markers = test.markers(); + +if (numTypedVariableCount + strTypedVariableCount !== markers.length) { + throw "Unexpected number of markers in file."; +} + +for (var i = 0; i < numTypedVariableCount; i++) { + goTo.marker("" + i); + verify.quickInfoIs("(parameter) x: number"); +} + +for (var i = 0; i < strTypedVariableCount; i++) { + goTo.marker("" + (i + numTypedVariableCount)); + verify.quickInfoIs("(parameter) x: string"); +} \ No newline at end of file