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

Tagged templates ES3 & 5 #1589

Merged
merged 30 commits into from
Feb 26, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6469375
Remove tagged templates error when targeting ES3 or 5
ivogabe Jan 4, 2015
c2d0bf8
Emit tagged templates when targeting ES3 or 5
ivogabe Jan 4, 2015
69d724f
Fix tagged templates that consist of a single part
ivogabe Jan 4, 2015
8f28c95
Emit parens when an argument is a comma operator
ivogabe Jan 5, 2015
a13af6b
Move code to separate functions
ivogabe Jan 5, 2015
349841e
Emit var in front of statement with tagged template
ivogabe Jan 9, 2015
28b90a2
Remove properties from types.ts
ivogabe Jan 19, 2015
ed7ae27
Remove binding of tagged templates
ivogabe Jan 19, 2015
d339a52
Remove statement from binder
ivogabe Jan 19, 2015
161545d
Change tagged template emit to new style
ivogabe Jan 19, 2015
434c908
Re-add debug assert & fix indent
ivogabe Jan 19, 2015
cbec9a3
Respond to CR
ivogabe Jan 23, 2015
39027d9
Rename emitParenthesized to emitParenthesizedIf
ivogabe Jan 23, 2015
9fc0144
Merge branch 'master' into taggedTemplates
ivogabe Jan 24, 2015
30c10fb
Merge branch 'master' into taggedTemplates
ivogabe Feb 6, 2015
04dd08d
Resolve missed merge conflict
ivogabe Feb 6, 2015
8e16e1d
Update baselines
ivogabe Feb 6, 2015
f77bedd
Emit parens for tag of tagged template if necessary
ivogabe Feb 13, 2015
eedcb09
Merge master into taggedTemplates
ivogabe Feb 16, 2015
c4008c3
Update tests
ivogabe Feb 16, 2015
f883259
Add tests for tagged templates
ivogabe Feb 21, 2015
35c815e
Respond to code review
ivogabe Feb 22, 2015
63e1ddb
Merge branch 'master' into taggedTemplates
ivogabe Feb 22, 2015
c291d12
Use createAndRecordTempVariable
ivogabe Feb 22, 2015
acdc177
Update baselines after merging master
ivogabe Feb 22, 2015
964ed7f
Rename callback to literalEmitter
ivogabe Feb 24, 2015
904b520
operator -> operatorToken.kind
ivogabe Feb 24, 2015
ac8e395
Merge branch 'master' into taggedTemplates
ivogabe Feb 25, 2015
80ff139
Merge branch 'master' into taggedTemplates
ivogabe Feb 26, 2015
2b10d39
Update baselines
ivogabe Feb 26, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ module ts {

function bind(node: Node) {
node.parent = parent;

switch (node.kind) {
case SyntaxKind.TypeParameter:
bindDeclaration(<Declaration>node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes, /*isBlockScopeContainer*/ false);
Expand Down
5 changes: 0 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6759,11 +6759,6 @@ module ts {
}

function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
// Grammar checking
if (languageVersion < ScriptTarget.ES6) {
grammarErrorOnFirstToken(node.template, Diagnostics.Tagged_templates_are_only_available_when_targeting_ECMAScript_6_and_higher);
}

return getReturnTypeOfSignature(getResolvedSignature(node));
}

Expand Down
1 change: 0 additions & 1 deletion src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ module ts {
const_declarations_must_be_initialized: { code: 1155, category: DiagnosticCategory.Error, key: "'const' declarations must be initialized" },
const_declarations_can_only_be_declared_inside_a_block: { code: 1156, category: DiagnosticCategory.Error, key: "'const' declarations can only be declared inside a block." },
let_declarations_can_only_be_declared_inside_a_block: { code: 1157, category: DiagnosticCategory.Error, key: "'let' declarations can only be declared inside a block." },
Tagged_templates_are_only_available_when_targeting_ECMAScript_6_and_higher: { code: 1159, category: DiagnosticCategory.Error, key: "Tagged templates are only available when targeting ECMAScript 6 and higher." },
Unterminated_template_literal: { code: 1160, category: DiagnosticCategory.Error, key: "Unterminated template literal." },
Unterminated_regular_expression_literal: { code: 1161, category: DiagnosticCategory.Error, key: "Unterminated regular expression literal." },
An_object_member_cannot_be_declared_optional: { code: 1162, category: DiagnosticCategory.Error, key: "An object member cannot be declared optional." },
Expand Down
4 changes: 0 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -459,10 +459,6 @@
"category": "Error",
"code": 1157
},
"Tagged templates are only available when targeting ECMAScript 6 and higher.": {
"category": "Error",
"code": 1159
},
"Unterminated template literal.": {
"category": "Error",
"code": 1160
Expand Down
88 changes: 80 additions & 8 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2072,7 +2072,7 @@ module ts {
}
}

function emitParenthesized(node: Node, parenthesized: boolean) {
function emitParenthesizedIf(node: Node, parenthesized: boolean) {
if (parenthesized) {
write("(");
}
Expand Down Expand Up @@ -2205,6 +2205,72 @@ module ts {
function getTemplateLiteralAsStringLiteral(node: LiteralExpression): string {
return '"' + escapeString(node.text) + '"';
}

function emitDownlevelRawTemplateLiteral(node: LiteralExpression) {
// Find original source text, since we need to emit the raw strings of the tagged template.
// The raw strings contain the (escaped) strings of what the user wrote.
// Examples: `\n` is converted to "\\n", a template string with a newline to "\n".
var text = getSourceTextOfNodeFromSourceFile(currentSourceFile, node);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm torn. On one hand, this brings us closer to what the user already wrote. ON the other, it means you need to do a lot more processing. Why not just use node.text instead? It will already have stripped off the } and ${ bits, and will already have changes newlines appropriately.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh. Is this because it's supposed to be 'raw'? If so, can you comment here the difference and how this shoudl behave on different inputs? Thanks!


// text contains the original source, it will also contain quotes ("`"), dolar signs and braces ("${" and "}"),
// thus we need to remove those characters.
// First template piece starts with "`", others with "}"
// Last template piece ends with "`", others with "${"
var isLast = node.kind === SyntaxKind.NoSubstitutionTemplateLiteral || node.kind === SyntaxKind.TemplateTail;
text = text.substring(1, text.length - (isLast ? 1 : 2));

// Newline normalization:
// ES6 Spec 11.8.6.1 - Static Semantics of TV's and TRV's
// <CR><LF> and <CR> LineTerminatorSequences are normalized to <LF> for both TV and TRV.
text = text.replace(/\r\n?/g, "\n");
text = escapeString(text);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't these steps for the cooked strings?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh maybe these are the steps that uncook the string


write('"' + text + '"');
}

function emitDownlevelTaggedTemplateArray(node: TaggedTemplateExpression, literalEmitter: (literal: LiteralExpression) => void) {
write("[");
if (node.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral) {
literalEmitter(<LiteralExpression>node.template);
}
else {
literalEmitter((<TemplateExpression>node.template).head);
forEach((<TemplateExpression>node.template).templateSpans, (child) => {
write(", ");
literalEmitter(child.literal);
});
}
write("]");
}

function emitDownlevelTaggedTemplate(node: TaggedTemplateExpression) {
var tempVariable = createAndRecordTempVariable(node);
write("(");
emit(tempVariable);
write(" = ");
emitDownlevelTaggedTemplateArray(node, emit);
write(", ");

emit(tempVariable);
write(".raw = ");
emitDownlevelTaggedTemplateArray(node, emitDownlevelRawTemplateLiteral);
write(", ");

emitParenthesizedIf(node.tag, needsParenthesisForPropertyAccessOrInvocation(node.tag));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need parentheses? I don't think you need to add them. The reason is that you want something that can be the left hand side of a call. But MemberExpression and CallExpression are the two nonterminals that can be the LHS of a tagged template, and those are precisely the things you need on the LHS of a call.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless there is some subtlety with new that I'm missing, but I don't think there is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As nonsensical as it is, it's because of #1589 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah looking at the grammar, you can't have a NewExpression as the tag, so you don't need to parenthesize the tag.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh gosh, you're right! The template literal gets demoted to an AdditiveExpression! Disregard what I said.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But do we do the right thing in this case?

`hello${"goodbye"}hello`[0]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is that rather than each parent node needing to know that its child could be a template, and therefore needs to parenthesize that child, the template should parenthesize itself accordingly, because it is the one that lowers its precedence when it gets emitted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, when I say template in this context, I mean an untagged template.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was agreeing with you that node.tag should include the parens, instead of requiring the code that uses it to add them.

For your example, the current PR also emits it correctly ("hello" + "goodbye" + "hello")[0]; but yes, your suggested implementation would be better.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that's a better approach. Although I do think this PR can go in as is, and we make that change separately.

write("(");
emit(tempVariable);

// Now we emit the expressions
if (node.template.kind === SyntaxKind.TemplateExpression) {
forEach((<TemplateExpression>node.template).templateSpans, templateSpan => {
write(", ");
var needsParens = templateSpan.expression.kind === SyntaxKind.BinaryExpression
&& (<BinaryExpression>templateSpan.expression).operatorToken.kind === SyntaxKind.CommaToken;
emitParenthesizedIf(templateSpan.expression, needsParens);
});
}
write("))");
}

function emitTemplateExpression(node: TemplateExpression): void {
// In ES6 mode and above, we can simply emit each portion of a template in order, but in
Expand Down Expand Up @@ -2249,7 +2315,8 @@ module ts {
write(" + ");
}

emitParenthesized(templateSpan.expression, needsParens);
emitParenthesizedIf(templateSpan.expression, needsParens);

// Only emit if the literal is non-empty.
// The binary '+' operator is left-associative, so the first string concatenation
// with the head will force the result up to this point to be a string.
Expand Down Expand Up @@ -2479,7 +2546,7 @@ module ts {
emit((<SpreadElementExpression>node).expression);
}

function needsParenthesisForPropertyAccess(node: Expression) {
function needsParenthesisForPropertyAccessOrInvocation(node: Expression) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave as is

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nvm, this change is fine.

switch (node.kind) {
case SyntaxKind.Identifier:
case SyntaxKind.ArrayLiteralExpression:
Expand Down Expand Up @@ -2509,7 +2576,7 @@ module ts {
var e = elements[pos];
if (e.kind === SyntaxKind.SpreadElementExpression) {
e = (<SpreadElementExpression>e).expression;
emitParenthesized(e, /*parenthesized*/ group === 0 && needsParenthesisForPropertyAccess(e));
emitParenthesizedIf(e, /*parenthesized*/ group === 0 && needsParenthesisForPropertyAccessOrInvocation(e));
pos++;
}
else {
Expand Down Expand Up @@ -2985,9 +3052,14 @@ module ts {
}

function emitTaggedTemplateExpression(node: TaggedTemplateExpression): void {
emit(node.tag);
write(" ");
emit(node.template);
if (compilerOptions.target >= ScriptTarget.ES6) {
emit(node.tag);
write(" ");
emit(node.template);
}
else {
emitDownlevelTaggedTemplate(node);
}
}

function emitParenExpression(node: ParenthesizedExpression) {
Expand Down Expand Up @@ -3157,7 +3229,7 @@ module ts {
}

function emitExpressionStatement(node: ExpressionStatement) {
emitParenthesized(node.expression, /*parenthesized*/ node.expression.kind === SyntaxKind.ArrowFunction);
emitParenthesizedIf(node.expression, /*parenthesized*/ node.expression.kind === SyntaxKind.ArrowFunction);
write(";");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//// [taggedTemplateStringsHexadecimalEscapes.ts]
function f(...args: any[]) {
}

f `\x0D${ "Interrupted CRLF" }\x0A`;

//// [taggedTemplateStringsHexadecimalEscapes.js]
function f() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i - 0] = arguments[_i];
}
}
(_a = ["\r", "\n"], _a.raw = ["\\x0D", "\\x0A"], f(_a, "Interrupted CRLF"));
var _a;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
=== tests/cases/compiler/taggedTemplateStringsHexadecimalEscapes.ts ===
function f(...args: any[]) {
>f : (...args: any[]) => void
>args : any[]
}

f `\x0D${ "Interrupted CRLF" }\x0A`;
>f : (...args: any[]) => void

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//// [taggedTemplateStringsHexadecimalEscapesES6.ts]
function f(...args: any[]) {
}

f `\x0D${ "Interrupted CRLF" }\x0A`;

//// [taggedTemplateStringsHexadecimalEscapesES6.js]
function f(...args) {
}
f `\x0D${"Interrupted CRLF"}\x0A`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
=== tests/cases/compiler/taggedTemplateStringsHexadecimalEscapesES6.ts ===
function f(...args: any[]) {
>f : (...args: any[]) => void
>args : any[]
}

f `\x0D${ "Interrupted CRLF" }\x0A`;
>f : (...args: any[]) => void

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ function f() {
x[_i - 0] = arguments[_i];
}
}
f "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2028 2029 0085 t v f b r n";
(_a = ["0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2028 2029 0085 t v f b r n"], _a.raw = ["0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2028 2029 0085 t v f b r n"], f(_a));
var _a;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
=== tests/cases/conformance/es6/templates/taggedTemplateStringsPlainCharactersThatArePartsOfEscapes01.ts ===


function f(...x: any[]) {
>f : (...x: any[]) => void
>x : any[]

}

f `0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2028 2029 0085 t v f b r n`
>f : (...x: any[]) => void

Loading