Skip to content

Commit 84b1291

Browse files
Merge pull request #23430 from Microsoft/taggedTemplateTypeArguments
Allow type arguments in generic tagged templates
2 parents a7c08e4 + 87bb96d commit 84b1291

18 files changed

+965
-19
lines changed

src/compiler/checker.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -17749,11 +17749,11 @@ namespace ts {
1774917749

1775017750
let typeArguments: NodeArray<TypeNode>;
1775117751

17752-
if (!isTaggedTemplate && !isDecorator && !isJsxOpeningOrSelfClosingElement) {
17752+
if (!isDecorator && !isJsxOpeningOrSelfClosingElement) {
1775317753
typeArguments = (<CallExpression>node).typeArguments;
1775417754

1775517755
// We already perform checking on the type arguments on the class declaration itself.
17756-
if ((<CallExpression>node).expression.kind !== SyntaxKind.SuperKeyword) {
17756+
if (isTaggedTemplate || (<CallExpression>node).expression.kind !== SyntaxKind.SuperKeyword) {
1775717757
forEach(typeArguments, checkSourceElement);
1775817758
}
1775917759
}
@@ -17866,7 +17866,7 @@ namespace ts {
1786617866
checkApplicableSignature(node, args, candidateForArgumentError, assignableRelation, /*excludeArgument*/ undefined, /*reportErrors*/ true);
1786717867
}
1786817868
else if (candidateForTypeArgumentError) {
17869-
checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression).typeArguments, /*reportErrors*/ true, fallbackError);
17869+
checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression).typeArguments, /*reportErrors*/ true, fallbackError);
1787017870
}
1787117871
else if (typeArguments && every(signatures, sig => length(sig.typeParameters) !== typeArguments.length)) {
1787217872
diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments));
@@ -18660,6 +18660,7 @@ namespace ts {
1866018660
}
1866118661

1866218662
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
18663+
checkGrammarTypeArguments(node, node.typeArguments);
1866318664
if (languageVersion < ScriptTarget.ES2015) {
1866418665
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
1866518666
}

src/compiler/emitter.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1462,6 +1462,7 @@ namespace ts {
14621462

14631463
function emitTaggedTemplateExpression(node: TaggedTemplateExpression) {
14641464
emitExpression(node.tag);
1465+
emitTypeArguments(node, node.typeArguments);
14651466
writeSpace();
14661467
emitExpression(node.template);
14671468
}

src/compiler/factory.ts

+20-5
Original file line numberDiff line numberDiff line change
@@ -1032,17 +1032,32 @@ namespace ts {
10321032
: node;
10331033
}
10341034

1035-
export function createTaggedTemplate(tag: Expression, template: TemplateLiteral) {
1035+
export function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
1036+
export function createTaggedTemplate(tag: Expression, typeArguments: ReadonlyArray<TypeNode>, template: TemplateLiteral): TaggedTemplateExpression;
1037+
/** @internal */
1038+
export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: ReadonlyArray<TypeNode> | TemplateLiteral, template?: TemplateLiteral): TaggedTemplateExpression;
1039+
export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: ReadonlyArray<TypeNode> | TemplateLiteral, template?: TemplateLiteral) {
10361040
const node = <TaggedTemplateExpression>createSynthesizedNode(SyntaxKind.TaggedTemplateExpression);
10371041
node.tag = parenthesizeForAccess(tag);
1038-
node.template = template;
1042+
if (template) {
1043+
node.typeArguments = asNodeArray(typeArgumentsOrTemplate as ReadonlyArray<TypeNode>);
1044+
node.template = template!;
1045+
}
1046+
else {
1047+
node.typeArguments = undefined;
1048+
node.template = typeArgumentsOrTemplate as TemplateLiteral;
1049+
}
10391050
return node;
10401051
}
10411052

1042-
export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral) {
1053+
export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
1054+
export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArguments: ReadonlyArray<TypeNode>, template: TemplateLiteral): TaggedTemplateExpression;
1055+
export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArgumentsOrTemplate: ReadonlyArray<TypeNode> | TemplateLiteral, template?: TemplateLiteral) {
10431056
return node.tag !== tag
1044-
|| node.template !== template
1045-
? updateNode(createTaggedTemplate(tag, template), node)
1057+
|| (template
1058+
? node.typeArguments !== typeArgumentsOrTemplate || node.template !== template
1059+
: node.typeArguments !== undefined || node.template !== typeArgumentsOrTemplate)
1060+
? updateNode(createTaggedTemplate(tag, typeArgumentsOrTemplate, template), node)
10461061
: node;
10471062
}
10481063

src/compiler/parser.ts

+42-11
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ namespace ts {
223223
visitNodes(cbNode, cbNodes, (<CallExpression>node).arguments);
224224
case SyntaxKind.TaggedTemplateExpression:
225225
return visitNode(cbNode, (<TaggedTemplateExpression>node).tag) ||
226+
visitNodes(cbNode, cbNodes, (<TaggedTemplateExpression>node).typeArguments) ||
226227
visitNode(cbNode, (<TaggedTemplateExpression>node).template);
227228
case SyntaxKind.TypeAssertionExpression:
228229
return visitNode(cbNode, (<TypeAssertion>node).type) ||
@@ -4362,20 +4363,29 @@ namespace ts {
43624363
continue;
43634364
}
43644365

4365-
if (token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead) {
4366-
const tagExpression = <TaggedTemplateExpression>createNode(SyntaxKind.TaggedTemplateExpression, expression.pos);
4367-
tagExpression.tag = expression;
4368-
tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral
4369-
? <NoSubstitutionTemplateLiteral>parseLiteralNode()
4370-
: parseTemplateExpression();
4371-
expression = finishNode(tagExpression);
4366+
if (isTemplateStartOfTaggedTemplate()) {
4367+
expression = parseTaggedTemplateRest(expression, /*typeArguments*/ undefined);
43724368
continue;
43734369
}
43744370

43754371
return <MemberExpression>expression;
43764372
}
43774373
}
43784374

4375+
function isTemplateStartOfTaggedTemplate() {
4376+
return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead;
4377+
}
4378+
4379+
function parseTaggedTemplateRest(tag: LeftHandSideExpression, typeArguments: NodeArray<TypeNode> | undefined) {
4380+
const tagExpression = <TaggedTemplateExpression>createNode(SyntaxKind.TaggedTemplateExpression, tag.pos);
4381+
tagExpression.tag = tag;
4382+
tagExpression.typeArguments = typeArguments;
4383+
tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral
4384+
? <NoSubstitutionTemplateLiteral>parseLiteralNode()
4385+
: parseTemplateExpression();
4386+
return finishNode(tagExpression);
4387+
}
4388+
43794389
function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression {
43804390
while (true) {
43814391
expression = parseMemberExpressionRest(expression);
@@ -4389,6 +4399,11 @@ namespace ts {
43894399
return expression;
43904400
}
43914401

4402+
if (isTemplateStartOfTaggedTemplate()) {
4403+
expression = parseTaggedTemplateRest(expression, typeArguments);
4404+
continue;
4405+
}
4406+
43924407
const callExpr = <CallExpression>createNode(SyntaxKind.CallExpression, expression.pos);
43934408
callExpr.expression = expression;
43944409
callExpr.typeArguments = typeArguments;
@@ -4436,8 +4451,10 @@ namespace ts {
44364451
function canFollowTypeArgumentsInExpression(): boolean {
44374452
switch (token()) {
44384453
case SyntaxKind.OpenParenToken: // foo<x>(
4439-
// this case are the only case where this token can legally follow a type argument
4440-
// list. So we definitely want to treat this as a type arg list.
4454+
case SyntaxKind.NoSubstitutionTemplateLiteral: // foo<T> `...`
4455+
case SyntaxKind.TemplateHead: // foo<T> `...${100}...`
4456+
// these are the only tokens can legally follow a type argument
4457+
// list. So we definitely want to treat them as type arg lists.
44414458

44424459
case SyntaxKind.DotToken: // foo<x>.
44434460
case SyntaxKind.CloseParenToken: // foo<x>)
@@ -4666,9 +4683,23 @@ namespace ts {
46664683
return finishNode(node);
46674684
}
46684685

4686+
let expression: MemberExpression = parsePrimaryExpression();
4687+
let typeArguments;
4688+
while (true) {
4689+
expression = parseMemberExpressionRest(expression);
4690+
typeArguments = tryParse(parseTypeArgumentsInExpression);
4691+
if (isTemplateStartOfTaggedTemplate()) {
4692+
Debug.assert(!!typeArguments,
4693+
"Expected a type argument list; all plain tagged template starts should be consumed in 'parseMemberExpressionRest'");
4694+
expression = parseTaggedTemplateRest(expression, typeArguments);
4695+
typeArguments = undefined;
4696+
}
4697+
break;
4698+
}
4699+
46694700
const node = <NewExpression>createNode(SyntaxKind.NewExpression, fullStart);
4670-
node.expression = parseMemberExpressionOrHigher();
4671-
node.typeArguments = tryParse(parseTypeArgumentsInExpression);
4701+
node.expression = expression;
4702+
node.typeArguments = typeArguments;
46724703
if (node.typeArguments || token() === SyntaxKind.OpenParenToken) {
46734704
node.arguments = parseArgumentList();
46744705
}

src/compiler/transformers/ts.ts

+11
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,9 @@ namespace ts {
502502
case SyntaxKind.NewExpression:
503503
return visitNewExpression(<NewExpression>node);
504504

505+
case SyntaxKind.TaggedTemplateExpression:
506+
return visitTaggedTemplateExpression(<TaggedTemplateExpression>node);
507+
505508
case SyntaxKind.NonNullExpression:
506509
// TypeScript non-null expressions are removed, but their subtrees are preserved.
507510
return visitNonNullExpression(<NonNullExpression>node);
@@ -2547,6 +2550,14 @@ namespace ts {
25472550
visitNodes(node.arguments, visitor, isExpression));
25482551
}
25492552

2553+
function visitTaggedTemplateExpression(node: TaggedTemplateExpression) {
2554+
return updateTaggedTemplate(
2555+
node,
2556+
visitNode(node.tag, visitor, isExpression),
2557+
/*typeArguments*/ undefined,
2558+
visitNode(node.template, visitor, isExpression));
2559+
}
2560+
25502561
/**
25512562
* Determines whether to emit an enum declaration.
25522563
*

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1727,6 +1727,7 @@ namespace ts {
17271727
export interface TaggedTemplateExpression extends MemberExpression {
17281728
kind: SyntaxKind.TaggedTemplateExpression;
17291729
tag: LeftHandSideExpression;
1730+
typeArguments?: NodeArray<TypeNode>;
17301731
template: TemplateLiteral;
17311732
}
17321733

src/compiler/visitor.ts

+1
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ namespace ts {
478478
case SyntaxKind.TaggedTemplateExpression:
479479
return updateTaggedTemplate(<TaggedTemplateExpression>node,
480480
visitNode((<TaggedTemplateExpression>node).tag, visitor, isExpression),
481+
visitNodes((<TaggedTemplateExpression>node).typeArguments, visitor, isExpression),
481482
visitNode((<TaggedTemplateExpression>node).template, visitor, isTemplateLiteral));
482483

483484
case SyntaxKind.TypeAssertionExpression:

tests/baselines/reference/api/tsserverlibrary.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,7 @@ declare namespace ts {
10511051
interface TaggedTemplateExpression extends MemberExpression {
10521052
kind: SyntaxKind.TaggedTemplateExpression;
10531053
tag: LeftHandSideExpression;
1054+
typeArguments?: NodeArray<TypeNode>;
10541055
template: TemplateLiteral;
10551056
}
10561057
type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement;
@@ -3524,7 +3525,9 @@ declare namespace ts {
35243525
function createNew(expression: Expression, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentsArray: ReadonlyArray<Expression> | undefined): NewExpression;
35253526
function updateNew(node: NewExpression, expression: Expression, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentsArray: ReadonlyArray<Expression> | undefined): NewExpression;
35263527
function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
3528+
function createTaggedTemplate(tag: Expression, typeArguments: ReadonlyArray<TypeNode>, template: TemplateLiteral): TaggedTemplateExpression;
35273529
function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
3530+
function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArguments: ReadonlyArray<TypeNode>, template: TemplateLiteral): TaggedTemplateExpression;
35283531
function createTypeAssertion(type: TypeNode, expression: Expression): TypeAssertion;
35293532
function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression): TypeAssertion;
35303533
function createParen(expression: Expression): ParenthesizedExpression;

tests/baselines/reference/api/typescript.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,7 @@ declare namespace ts {
10511051
interface TaggedTemplateExpression extends MemberExpression {
10521052
kind: SyntaxKind.TaggedTemplateExpression;
10531053
tag: LeftHandSideExpression;
1054+
typeArguments?: NodeArray<TypeNode>;
10541055
template: TemplateLiteral;
10551056
}
10561057
type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement;
@@ -3524,7 +3525,9 @@ declare namespace ts {
35243525
function createNew(expression: Expression, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentsArray: ReadonlyArray<Expression> | undefined): NewExpression;
35253526
function updateNew(node: NewExpression, expression: Expression, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentsArray: ReadonlyArray<Expression> | undefined): NewExpression;
35263527
function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
3528+
function createTaggedTemplate(tag: Expression, typeArguments: ReadonlyArray<TypeNode>, template: TemplateLiteral): TaggedTemplateExpression;
35273529
function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
3530+
function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArguments: ReadonlyArray<TypeNode>, template: TemplateLiteral): TaggedTemplateExpression;
35283531
function createTypeAssertion(type: TypeNode, expression: Expression): TypeAssertion;
35293532
function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression): TypeAssertion;
35303533
function createParen(expression: Expression): ParenthesizedExpression;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//// [taggedTemplatesWithTypeArguments1.ts]
2+
declare function f<T>(strs: TemplateStringsArray, ...callbacks: Array<(x: T) => any>): void;
3+
4+
interface Stuff {
5+
x: number;
6+
y: string;
7+
z: boolean;
8+
}
9+
10+
export const a = f<Stuff> `
11+
hello
12+
${stuff => stuff.x}
13+
brave
14+
${stuff => stuff.y}
15+
world
16+
${stuff => stuff.z}
17+
`;
18+
19+
declare function g<Input, T, U, V>(
20+
strs: TemplateStringsArray,
21+
t: (i: Input) => T, u: (i: Input) => U, v: (i: Input) => V): T | U | V;
22+
23+
export const b = g<Stuff, number, string, boolean> `
24+
hello
25+
${stuff => stuff.x}
26+
brave
27+
${stuff => stuff.y}
28+
world
29+
${stuff => stuff.z}
30+
`;
31+
32+
declare let obj: {
33+
prop: <T>(strs: TemplateStringsArray, x: (input: T) => T) => {
34+
returnedObjProp: T
35+
}
36+
}
37+
38+
export let c = obj["prop"]<Stuff> `${(input) => ({ ...input })}`
39+
c.returnedObjProp.x;
40+
c.returnedObjProp.y;
41+
c.returnedObjProp.z;
42+
43+
c = obj.prop<Stuff> `${(input) => ({ ...input })}`
44+
c.returnedObjProp.x;
45+
c.returnedObjProp.y;
46+
c.returnedObjProp.z;
47+
48+
//// [taggedTemplatesWithTypeArguments1.js]
49+
export const a = f `
50+
hello
51+
${stuff => stuff.x}
52+
brave
53+
${stuff => stuff.y}
54+
world
55+
${stuff => stuff.z}
56+
`;
57+
export const b = g `
58+
hello
59+
${stuff => stuff.x}
60+
brave
61+
${stuff => stuff.y}
62+
world
63+
${stuff => stuff.z}
64+
`;
65+
export let c = obj["prop"] `${(input) => ({ ...input })}`;
66+
c.returnedObjProp.x;
67+
c.returnedObjProp.y;
68+
c.returnedObjProp.z;
69+
c = obj.prop `${(input) => ({ ...input })}`;
70+
c.returnedObjProp.x;
71+
c.returnedObjProp.y;
72+
c.returnedObjProp.z;

0 commit comments

Comments
 (0)