TypeScript: Tagged template literals compiled incorrectly, TemplateStringsArray not cached

TypeScript Version: 2.4.1

Code

let previousStrings;

function test(strings, ...value) {
  if (previousStrings !== undefined && previousStrings !== strings) {
    window.alert('failure');
  }
  previousStrings = strings;
}

function go(n) {
  return test`literal ${n}`;
}

Expected behavior:

According to the ECMAScript spec, every time a template tag is evaluated, the same strings object should be passed as the first argument:

Each TemplateLiteral in the program code of a realm is associated with a unique template object that is used in the evaluation of tagged Templates (12.2.9.6). The template objects are frozen and the same template object is used each time a specific tagged Template is evaluated.

https://tc39.github.io/ecma262/#sec-gettemplateobject

Actual behavior:

The above snippet compiles to:

var previousStrings;
function test(strings) {
    var value = [];
    for (var _i = 1; _i < arguments.length; _i++) {
        value[_i - 1] = arguments[_i];
    }
    if (previousStrings !== undefined && previousStrings !== strings) {
        window.alert('failure');
    }
    previousStrings = strings;
}
function go(n) {
    return (_a = ["literal ", ""], _a.raw = ["literal ", ""], test(_a, n));
    var _a;
}

You can see that _a is going to have a new identity for each call of go().

FWIW, Babel compiles this correctly:

'use strict';

var _templateObject = _taggedTemplateLiteral(['literal ', ''], ['literal ', '']);

function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }

var previousStrings = void 0;

function test(strings) {
  if (previousStrings !== undefined && previousStrings !== strings) {
    window.alert('failure');
  }
  previousStrings = strings;
}

function go(n) {
  return test(_templateObject, n);
}

This is breaking lit-html when used in TypeScript: https://github.com/PolymerLabs/lit-html/issues/58

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 9
  • Comments: 27 (5 by maintainers)

Commits related to this issue

Most upvoted comments

@DanielRosenwasser whether or not equal literals from separate source locations remain identical might be up in the air, but this should be true:

let t = () => id``;

t() === t(); // true

That it isn’t is a pretty critical bug that prevents using TemplateStringsArray as map key. Until this is fixed I need to tell my TypeScript users to target esnext, then compile with Babel.

The incremental fix is to hoist the TemplateStringsArray declaration to the top-level:

var _a = ["literal ", ""];
_a.raw = ["literal ", ""];

function go(n) {
    return test(_a, n);
}

In lit-html we check for this bug by comparing the results of two calls to a tag:

const envCachesTemplates = ((t) => t() === t())(() => ((s) => s)``);

The expanded version is a little easier to read:

const t = () => ((s) => s)``;
const envCachesTemplates = t() === t();