TypeScript: Spread operator is not correctly translated into JS

TypeScript Version: 1.8 Target: ES5

The following code:

[...(new Array(5))]

translates into:

(new Array(5)).slice();

However, the ES6 meaning is not the same. See the output below:

> [...(new Array(5))]
[ undefined, undefined, undefined, undefined, undefined ]
> (new Array(5)).slice();
[ , , , ,  ]

Expected behavior:

Array.apply(null, Array(5))

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 55
  • Comments: 41 (17 by maintainers)

Commits related to this issue

Most upvoted comments

Is it going to be fixed?

I mean hey, it’s almost 3 years old.

My target is ES5 so not sure if this is related, but instances of Set are incorrectly converted to slice when using the spread operator. According to the MDN docs (scroll down to the Relation with Array objects section) - I should be able to convert a Set instance to an array by simply doing:

let foo = [1,2,3];
let bar = new Set(foo);
[...bar] instanceof Array; // results in `true` in the Chrome console

But the TypeScript compiler converts [...bar] to bar.slice() which is not correct.

I’ve found that Array.from(Set) works, but you need to make sure you have the typings installed.

We ended up using Array.from() with the necessary typings and polyfills to make the spread operator work.

Then we are in situation when without --downlevelIteration some array (not other collections) operatin works and some simple emit wrong code. Again - wrong emitted code I consider as a bug. Examle: [...[1,2,3]] //emits correct code: [1,2,3]

however [...Array(5)] // emits broken code: Array(5).slice()

How can I decide to enable --downlevelIteration in case sometimes it works without it sometimes not?

Shouldn’t compilator emit error for [...Array(5)] in case --downlevelIteration is disabled instead of emitting wrong JS?

@arackaf things have changed… when targeting ES5 the compiler supports downlevel iterator which is similar to the Babel feature which allows objects to expose an iterator interface.

Technically this issue was fixed by the addition to that feature and could be closed.

Array.from converts the iterable into an array.

The code [...iterable] should be translated into the equivalent of Array.from(iterable) as by definition [...iterable] translates the iterable into the array, just like the Array.from method.

For example typescript (without the flag mentioned above) would translate [...'abcdefg'] into 'abcdefg'.slice() instead of Array.from('abcdefg') which both produce different outputs, and only the latter is spec-compliant

@aik-jahoda when using --downlevelIteration all iterators are spec compliant, so again, this could be closed, though it requires used of --downlevelIteration:

$ cat test.ts
console.log([...(new Array(5))])
$ tsc test.ts --target es5 --downlevelIteration --lib es6,dom
$ cat test.js
var __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spread = (this && this.__spread) || function () {
    for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
    return ar;
};
console.log(__spread((new Array(5))));
$ node test.js
[ undefined, undefined, undefined, undefined, undefined ]

@marvinhagemeister it requires that a global Symbol polyfill to be available at the runtime environment and that any iterables expose an iterable interface “symbol”. Because of those pre-reqs, I think the core team would be reticent to make it a default.

Awesome, didn’t know about that flag. Perhaps --downlevelIteration can be directly included when setting the target to ES5? Without knowing the reasons behind making it a specific flag it seems like that’d be a good default setting.

@huy-nguyen the playground is always targets ES5 and does not include the optional --downlevelIteration flag.

You could not have encountered this error if you were targeting ES6, as the down emit described above would only occur when targeting ES5. I get the following when I run it through tsc:

$ cat test.ts
const test = new Map([[1, 'a'], [2, 'b'], [3, 'c']]);
console.log([...test.keys()]);

$ tsc test.ts --target es6
$ cat test.js
const test = new Map([[1, 'a'], [2, 'b'], [3, 'c']]);
console.log([...test.keys()]);

I also get no error and the following output when using the --downlevelIteration:

$ tsc test.ts --target es5 --downlevelIteration --lib es6,dom
$ cat test.js
var __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spread = (this && this.__spread) || function () {
    for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
    return ar;
};
var test = new Map([[1, 'a'], [2, 'b'], [3, 'c']]);
console.log(__spread(test.keys()));

This of course requires a global polyfill for Map() and Symbol at run-time.

This just bit us when dealing with a DOMStringList. The most common advice for converting DOMStringList to Array is to use [...stringlist] but TypeScript compiles that to stringlist.slice() and slice isn’t defined on DOMStringList.

I believe Array.prototype.slice.call(stringlist) would work for this particular case

@brettjurgens This issue is about spread operator downlevel emit for arrays with holes. Your issue is #2696

Perhaps the emit should use concat instead of slice 😕 🌹