Skip to content

Commit

Permalink
Merge pull request #23430 from Microsoft/taggedTemplateTypeArguments
Browse files Browse the repository at this point in the history
Allow type arguments in generic tagged templates
  • Loading branch information
DanielRosenwasser authored Apr 19, 2018
2 parents a7c08e4 + 87bb96d commit 84b1291
Show file tree
Hide file tree
Showing 18 changed files with 965 additions and 19 deletions.
7 changes: 4 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17749,11 +17749,11 @@ namespace ts {

let typeArguments: NodeArray<TypeNode>;

if (!isTaggedTemplate && !isDecorator && !isJsxOpeningOrSelfClosingElement) {
if (!isDecorator && !isJsxOpeningOrSelfClosingElement) {
typeArguments = (<CallExpression>node).typeArguments;

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

function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
checkGrammarTypeArguments(node, node.typeArguments);
if (languageVersion < ScriptTarget.ES2015) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1462,6 +1462,7 @@ namespace ts {

function emitTaggedTemplateExpression(node: TaggedTemplateExpression) {
emitExpression(node.tag);
emitTypeArguments(node, node.typeArguments);
writeSpace();
emitExpression(node.template);
}
Expand Down
25 changes: 20 additions & 5 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1032,17 +1032,32 @@ namespace ts {
: node;
}

export function createTaggedTemplate(tag: Expression, template: TemplateLiteral) {
export function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
export function createTaggedTemplate(tag: Expression, typeArguments: ReadonlyArray<TypeNode>, template: TemplateLiteral): TaggedTemplateExpression;
/** @internal */
export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: ReadonlyArray<TypeNode> | TemplateLiteral, template?: TemplateLiteral): TaggedTemplateExpression;
export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: ReadonlyArray<TypeNode> | TemplateLiteral, template?: TemplateLiteral) {
const node = <TaggedTemplateExpression>createSynthesizedNode(SyntaxKind.TaggedTemplateExpression);
node.tag = parenthesizeForAccess(tag);
node.template = template;
if (template) {
node.typeArguments = asNodeArray(typeArgumentsOrTemplate as ReadonlyArray<TypeNode>);
node.template = template!;
}
else {
node.typeArguments = undefined;
node.template = typeArgumentsOrTemplate as TemplateLiteral;
}
return node;
}

export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral) {
export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArguments: ReadonlyArray<TypeNode>, template: TemplateLiteral): TaggedTemplateExpression;
export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArgumentsOrTemplate: ReadonlyArray<TypeNode> | TemplateLiteral, template?: TemplateLiteral) {
return node.tag !== tag
|| node.template !== template
? updateNode(createTaggedTemplate(tag, template), node)
|| (template
? node.typeArguments !== typeArgumentsOrTemplate || node.template !== template
: node.typeArguments !== undefined || node.template !== typeArgumentsOrTemplate)
? updateNode(createTaggedTemplate(tag, typeArgumentsOrTemplate, template), node)
: node;
}

Expand Down
53 changes: 42 additions & 11 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ namespace ts {
visitNodes(cbNode, cbNodes, (<CallExpression>node).arguments);
case SyntaxKind.TaggedTemplateExpression:
return visitNode(cbNode, (<TaggedTemplateExpression>node).tag) ||
visitNodes(cbNode, cbNodes, (<TaggedTemplateExpression>node).typeArguments) ||
visitNode(cbNode, (<TaggedTemplateExpression>node).template);
case SyntaxKind.TypeAssertionExpression:
return visitNode(cbNode, (<TypeAssertion>node).type) ||
Expand Down Expand Up @@ -4362,20 +4363,29 @@ namespace ts {
continue;
}

if (token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead) {
const tagExpression = <TaggedTemplateExpression>createNode(SyntaxKind.TaggedTemplateExpression, expression.pos);
tagExpression.tag = expression;
tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral
? <NoSubstitutionTemplateLiteral>parseLiteralNode()
: parseTemplateExpression();
expression = finishNode(tagExpression);
if (isTemplateStartOfTaggedTemplate()) {
expression = parseTaggedTemplateRest(expression, /*typeArguments*/ undefined);
continue;
}

return <MemberExpression>expression;
}
}

function isTemplateStartOfTaggedTemplate() {
return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead;
}

function parseTaggedTemplateRest(tag: LeftHandSideExpression, typeArguments: NodeArray<TypeNode> | undefined) {
const tagExpression = <TaggedTemplateExpression>createNode(SyntaxKind.TaggedTemplateExpression, tag.pos);
tagExpression.tag = tag;
tagExpression.typeArguments = typeArguments;
tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral
? <NoSubstitutionTemplateLiteral>parseLiteralNode()
: parseTemplateExpression();
return finishNode(tagExpression);
}

function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression {
while (true) {
expression = parseMemberExpressionRest(expression);
Expand All @@ -4389,6 +4399,11 @@ namespace ts {
return expression;
}

if (isTemplateStartOfTaggedTemplate()) {
expression = parseTaggedTemplateRest(expression, typeArguments);
continue;
}

const callExpr = <CallExpression>createNode(SyntaxKind.CallExpression, expression.pos);
callExpr.expression = expression;
callExpr.typeArguments = typeArguments;
Expand Down Expand Up @@ -4436,8 +4451,10 @@ namespace ts {
function canFollowTypeArgumentsInExpression(): boolean {
switch (token()) {
case SyntaxKind.OpenParenToken: // foo<x>(
// this case are the only case where this token can legally follow a type argument
// list. So we definitely want to treat this as a type arg list.
case SyntaxKind.NoSubstitutionTemplateLiteral: // foo<T> `...`
case SyntaxKind.TemplateHead: // foo<T> `...${100}...`
// these are the only tokens can legally follow a type argument
// list. So we definitely want to treat them as type arg lists.

case SyntaxKind.DotToken: // foo<x>.
case SyntaxKind.CloseParenToken: // foo<x>)
Expand Down Expand Up @@ -4666,9 +4683,23 @@ namespace ts {
return finishNode(node);
}

let expression: MemberExpression = parsePrimaryExpression();
let typeArguments;
while (true) {
expression = parseMemberExpressionRest(expression);
typeArguments = tryParse(parseTypeArgumentsInExpression);
if (isTemplateStartOfTaggedTemplate()) {
Debug.assert(!!typeArguments,
"Expected a type argument list; all plain tagged template starts should be consumed in 'parseMemberExpressionRest'");
expression = parseTaggedTemplateRest(expression, typeArguments);
typeArguments = undefined;
}
break;
}

const node = <NewExpression>createNode(SyntaxKind.NewExpression, fullStart);
node.expression = parseMemberExpressionOrHigher();
node.typeArguments = tryParse(parseTypeArgumentsInExpression);
node.expression = expression;
node.typeArguments = typeArguments;
if (node.typeArguments || token() === SyntaxKind.OpenParenToken) {
node.arguments = parseArgumentList();
}
Expand Down
11 changes: 11 additions & 0 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,9 @@ namespace ts {
case SyntaxKind.NewExpression:
return visitNewExpression(<NewExpression>node);

case SyntaxKind.TaggedTemplateExpression:
return visitTaggedTemplateExpression(<TaggedTemplateExpression>node);

case SyntaxKind.NonNullExpression:
// TypeScript non-null expressions are removed, but their subtrees are preserved.
return visitNonNullExpression(<NonNullExpression>node);
Expand Down Expand Up @@ -2547,6 +2550,14 @@ namespace ts {
visitNodes(node.arguments, visitor, isExpression));
}

function visitTaggedTemplateExpression(node: TaggedTemplateExpression) {
return updateTaggedTemplate(
node,
visitNode(node.tag, visitor, isExpression),
/*typeArguments*/ undefined,
visitNode(node.template, visitor, isExpression));
}

/**
* Determines whether to emit an enum declaration.
*
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1727,6 +1727,7 @@ namespace ts {
export interface TaggedTemplateExpression extends MemberExpression {
kind: SyntaxKind.TaggedTemplateExpression;
tag: LeftHandSideExpression;
typeArguments?: NodeArray<TypeNode>;
template: TemplateLiteral;
}

Expand Down
1 change: 1 addition & 0 deletions src/compiler/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ namespace ts {
case SyntaxKind.TaggedTemplateExpression:
return updateTaggedTemplate(<TaggedTemplateExpression>node,
visitNode((<TaggedTemplateExpression>node).tag, visitor, isExpression),
visitNodes((<TaggedTemplateExpression>node).typeArguments, visitor, isExpression),
visitNode((<TaggedTemplateExpression>node).template, visitor, isTemplateLiteral));

case SyntaxKind.TypeAssertionExpression:
Expand Down
3 changes: 3 additions & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,7 @@ declare namespace ts {
interface TaggedTemplateExpression extends MemberExpression {
kind: SyntaxKind.TaggedTemplateExpression;
tag: LeftHandSideExpression;
typeArguments?: NodeArray<TypeNode>;
template: TemplateLiteral;
}
type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement;
Expand Down Expand Up @@ -3524,7 +3525,9 @@ declare namespace ts {
function createNew(expression: Expression, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentsArray: ReadonlyArray<Expression> | undefined): NewExpression;
function updateNew(node: NewExpression, expression: Expression, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentsArray: ReadonlyArray<Expression> | undefined): NewExpression;
function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
function createTaggedTemplate(tag: Expression, typeArguments: ReadonlyArray<TypeNode>, template: TemplateLiteral): TaggedTemplateExpression;
function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArguments: ReadonlyArray<TypeNode>, template: TemplateLiteral): TaggedTemplateExpression;
function createTypeAssertion(type: TypeNode, expression: Expression): TypeAssertion;
function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression): TypeAssertion;
function createParen(expression: Expression): ParenthesizedExpression;
Expand Down
3 changes: 3 additions & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,7 @@ declare namespace ts {
interface TaggedTemplateExpression extends MemberExpression {
kind: SyntaxKind.TaggedTemplateExpression;
tag: LeftHandSideExpression;
typeArguments?: NodeArray<TypeNode>;
template: TemplateLiteral;
}
type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement;
Expand Down Expand Up @@ -3524,7 +3525,9 @@ declare namespace ts {
function createNew(expression: Expression, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentsArray: ReadonlyArray<Expression> | undefined): NewExpression;
function updateNew(node: NewExpression, expression: Expression, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentsArray: ReadonlyArray<Expression> | undefined): NewExpression;
function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
function createTaggedTemplate(tag: Expression, typeArguments: ReadonlyArray<TypeNode>, template: TemplateLiteral): TaggedTemplateExpression;
function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArguments: ReadonlyArray<TypeNode>, template: TemplateLiteral): TaggedTemplateExpression;
function createTypeAssertion(type: TypeNode, expression: Expression): TypeAssertion;
function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression): TypeAssertion;
function createParen(expression: Expression): ParenthesizedExpression;
Expand Down
72 changes: 72 additions & 0 deletions tests/baselines/reference/taggedTemplatesWithTypeArguments1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//// [taggedTemplatesWithTypeArguments1.ts]
declare function f<T>(strs: TemplateStringsArray, ...callbacks: Array<(x: T) => any>): void;

interface Stuff {
x: number;
y: string;
z: boolean;
}

export const a = f<Stuff> `
hello
${stuff => stuff.x}
brave
${stuff => stuff.y}
world
${stuff => stuff.z}
`;

declare function g<Input, T, U, V>(
strs: TemplateStringsArray,
t: (i: Input) => T, u: (i: Input) => U, v: (i: Input) => V): T | U | V;

export const b = g<Stuff, number, string, boolean> `
hello
${stuff => stuff.x}
brave
${stuff => stuff.y}
world
${stuff => stuff.z}
`;

declare let obj: {
prop: <T>(strs: TemplateStringsArray, x: (input: T) => T) => {
returnedObjProp: T
}
}

export let c = obj["prop"]<Stuff> `${(input) => ({ ...input })}`
c.returnedObjProp.x;
c.returnedObjProp.y;
c.returnedObjProp.z;

c = obj.prop<Stuff> `${(input) => ({ ...input })}`
c.returnedObjProp.x;
c.returnedObjProp.y;
c.returnedObjProp.z;

//// [taggedTemplatesWithTypeArguments1.js]
export const a = f `
hello
${stuff => stuff.x}
brave
${stuff => stuff.y}
world
${stuff => stuff.z}
`;
export const b = g `
hello
${stuff => stuff.x}
brave
${stuff => stuff.y}
world
${stuff => stuff.z}
`;
export let c = obj["prop"] `${(input) => ({ ...input })}`;
c.returnedObjProp.x;
c.returnedObjProp.y;
c.returnedObjProp.z;
c = obj.prop `${(input) => ({ ...input })}`;
c.returnedObjProp.x;
c.returnedObjProp.y;
c.returnedObjProp.z;
Loading

0 comments on commit 84b1291

Please sign in to comment.