Skip to content

Spread operator is not correctly translated into JS #8856

Closed

Description

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))

Activity

  1. icfantv commented on May 28, 2016

    @icfantv

    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.

  2. basarat commented on May 29, 2016

    @basarat
    Contributor

    Perhaps the emit should use concat instead of slice :-/ :rose:

  3. icfantv commented on May 29, 2016

    @icfantv

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

  4. Arnavion commented on Jun 1, 2016

    @Arnavion
    Contributor

    Regarding your second question: Adam Gordon (@icfantv)'s question:

    The spread operator on Set, etc is only supported when targeting ES6, since it requires the type to implement [Symbol.iterator]() which only exists in ES6. When targeting ES5, the spread operator is only allowed for arrays (and maybe for strings and other array-likes in the future).

    So the compiler emits a call to .slice() because it expects to be emitting it for arrays. Note that it also emits an error complaining about using the spread operator on a non-array, so there's not really a problem.

  5. icfantv commented on Jun 1, 2016

    @icfantv

    Arnav Singh (@Arnavion), if the compiler had spit out an error, I wouldn't have researched for a GH issue or a work-around as it would have been clear what the problem was. I didn't get an error until the code was actually executed in my application. Whether or not this is due to some TSC setting, I don't know, I just know it's an issue in our environment.

  6. Arnavion commented on Jun 1, 2016

    @Arnavion
    Contributor
  7. kitsonk commented on Jun 1, 2016

    @kitsonk
    Contributor

    Of course that depends on how Adam Gordon (@icfantv) acquired the types for Set in the first place... Some polyfills would have easily have tricked TypeScript into thinking it could do what it couldn't do.

    For example:

    interface Set<T> extends Array<T> {
    }
    
    interface SetConstructor {
        new <T>(): Set<T>;
    }
    
    declare var Set: SetConstructor;
    
    [...new Set()]; // no errors
  8. icfantv commented on Jun 1, 2016

    @icfantv

    Kitson Kelly (@kitsonk) excellent point. I am upgrading an Angular 1 app to Angular 2 and have to manage the typings myself to some extent and am at the mercy of those typings. While I would like to believe that they are solid, I have run into issues as noted above where the compiler will not complain but the generated code is not valid.

  9. mhegazy commented on Jun 4, 2016

    @mhegazy
    Contributor

    The compiler has no way to know if a definition is correct or not. if the definition says a Set is an Array, then it will be treated like one.

    If you are using typescript@next consider using the --lib flag to specify the ES6-compliant set definition in --lib es2015.collection. see #6974 for more information.

  10. icfantv commented on Jun 5, 2016

    @icfantv

    Mohamed Hegazy (@mhegazy), can you please elaborate on what you mean when you say the compiler has no way of knowing if a definition is correct?

    If I say [...Set], the compiler should not be generating invalid JavaScript. It's almost like you're saying the compiler uses the typings files to not only determine what is right syntax, but the actual generated code as well. Surely that can't be right.

  11. kitsonk commented on Jun 5, 2016

    @kitsonk
    Contributor

    Surely that can't be right.

    It is correct. When figuring out how to down emit something, the compiler has to understand what it can and cannot do. When looking at the spread operator and a target of ES5, it says to itself "I can spread things that look like Arrays". The only way it knows if something is an Array or not is using the types it can reason out from the code. If it is passed something that allegedly is an Array, it will do what its down emit code says, which is to do a .slice(). If it doesn't match one of the patterns it doesn't know how to down-emit, it throws.

    How else could it be done?

  12. Arnavion commented on Jun 5, 2016

    @Arnavion
    Contributor

    Mohamed Hegazy (@mhegazy) I feel the issue got side-tracked. Can you reopen for the original issue?

  13. 44 remaining items

  14. EKashpersky commented on Jan 30, 2019

    @EKashpersky

    Is it going to be fixed?

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

  15. rbuckton commented on Apr 29, 2019

    @rbuckton
    Contributor

    I am not certain there is anything actionable here. We previously decided that TypeScript's default ES5 transpile for spread and for..of (which forgo precise runtime semantics over runtime performance) would remain as-is, and introduced --downlevelIteration as a way to opt-in to the slower but more correct runtime semantics.

  16. added a commit that references this issue on Jul 23, 2019
  17. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptES6Relates to the ES6 Spec

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions