Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow type arguments in generic tagged templates #23430

Merged
merged 20 commits into from Apr 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b409888
Added tests.
DanielRosenwasser Apr 14, 2018
23567ee
Accepted baselines.
DanielRosenwasser Apr 14, 2018
60b6d3f
Fixed up test. Thanks arrow functions...
DanielRosenwasser Apr 14, 2018
da7967a
Added basic support for parsing/emitting type arguments in tagged tem…
DanielRosenwasser Apr 14, 2018
090f6bd
Accepted baselines.
DanielRosenwasser Apr 14, 2018
7aa916a
Strip away type arguments from tagged templates when emitting.
DanielRosenwasser Apr 14, 2018
4785acb
Accepted baselines.
DanielRosenwasser Apr 14, 2018
82e09c9
Perform checking and resolution of tagged template type arguments.
DanielRosenwasser Apr 14, 2018
fe8615d
Accepted baselines.
DanielRosenwasser Apr 14, 2018
6107e05
Added test for tagged templates in new expressions.
DanielRosenwasser Apr 15, 2018
2510c19
Accepted baselines.
DanielRosenwasser Apr 15, 2018
eb8eeaf
Allow parsing tagged templates with type arguments in new expressions.
DanielRosenwasser Apr 15, 2018
a2073f1
Accepted baselines.
DanielRosenwasser Apr 15, 2018
4bfb1a3
Avoid breaking change by introducing overloads for 'createTaggedTempl…
DanielRosenwasser Apr 17, 2018
78e98c3
Accepted baselines.
DanielRosenwasser Apr 17, 2018
8e27f46
Added test for ASI concerns.
DanielRosenwasser Apr 19, 2018
e21a8b8
Accepted baselines.
DanielRosenwasser Apr 19, 2018
7f96fec
Added test in case 'super' is ever possibly parsed as a tagged templa…
DanielRosenwasser Apr 19, 2018
70feb7b
Avoid duplicate code when checking for tagged templates.
DanielRosenwasser Apr 19, 2018
87bb96d
Accepted baselines.
DanielRosenwasser Apr 19, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/compiler/checker.ts
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
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
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
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
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
Expand Up @@ -1726,6 +1726,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
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
Expand Up @@ -1050,6 +1050,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 @@ -3517,7 +3518,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
Expand Up @@ -1050,6 +1050,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 @@ -3517,7 +3518,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
@@ -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;