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

Support proposed ES Rest/Spread properties #2103

Closed
fdecampredon opened this issue Feb 21, 2015 · 96 comments · Fixed by #11150
Closed

Support proposed ES Rest/Spread properties #2103

fdecampredon opened this issue Feb 21, 2015 · 96 comments · Fixed by #11150
Assignees
Labels
Committed The team has roadmapped this issue ES Next New featurers for ECMAScript (a.k.a. ESNext) Fixed A PR has been merged for this issue Suggestion An idea for TypeScript

Comments

@fdecampredon
Copy link

es7 proposal : https://github.com/sebmarkbage/ecmascript-rest-spread

Spread properties

Typing

In my opinion the goal of this method is to be able to duplicate an object and changing some props, so I think it's particularly important in this case to not check duplicate property declaration :

var obj = { x: 1, y: 2};
var obj1 = {...obj, z: 3, y: 4}; // not an error

I have a very naive type check algorithm for a similar feature (JSXSpreadAttribute) in my little jsx-typescript fork: I just copy the properties of the spread object in the properties table when I encounter a spread object, and override those property if I encounter a declaration with a similar name.

Emitting

jstransform use Object.assign, babel introduce a shim:

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

We could either force the presence of assign function on ObjectConstructor interface, or provide a similar function (with a different name).

I think that the optimal solution would be to not emit any helper in es6 target (or if Object.assign is defined), and to emit an helper function for es5, es3.

var obj = { x: 1, y: 2};
var obj1 = {...obj, z: 3};

/// ES6 emit
var obj = {x: 1, y: 2};
var obj1= Object.assign({}, obj, { z: 3 });

//ES3 emit
var __assign = function (target) { 
    for (var i = 1; i < arguments.length; i++) { 
        var source = arguments[i]; 
        for (var key in source) { 
            if (Object.prototype.hasOwnProperty.call(source, key)) { 
                target[key] = source[key];
            } 
        } 
    } 
    return target; 
};

var obj = {x: 1, y: 2};
var obj1= __assign({}, obj, { z: 3 });

Rest properties

Typing

For simple object the new type is a subtype of the assignation that does not contains properties that has been captured before the rest properties :

var obj = {x:1, y: 1, z: 1};
var {z, ...obj1} = obj;
obj1// {x: number; y:number};

If the destructuring assignment has an index declaration, the result has also a similar index declaration:

var obj: { [string: string]: string };
var {[excludedId], ...obj1} = obj;
obj1// { [string: string]: string };

new/call declarations are obviously not captured:

var obj: { (): void; property: string};
var { ...obj1} = obj;
obj1// { property: string };

Emitting

It is not possible to emit rest properties without an helper function, this one is from babel:

var obj = {x:1, y: 1, z: 1};
var {z, ...obj1} = obj;
var __objectWithoutProperties = function(obj, keys) {
    var target = {};
    for (var i in obj) {
        if (keys.indexOf(i) >= 0) continue;
        if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
        target[i] = obj[i];
    }
    return target;
};

var obj = {x:1, y: 1, z: 1};
var z = obj.z;
var obj1 = __objectWithoutProperties(obj, ["z"]);

Edit: added some little typing/emitting example

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. ES7 Relates to the ES7 Spec labels Feb 23, 2015
@RyanCavanaugh
Copy link
Member

We'd probably need some examples of how this could plausibly be emitted in downlevel (if that's in scope).

@fdecampredon
Copy link
Author

@RyanCavanaugh, I added some little emitting example and typing idea.
I would like to work on that one since anyway it's more or less a jsx feature and I will need to add it to my fork, but if it's added to typescript core it's better ^^.

@mnpenner
Copy link

I recently discovered this feature in ES7 via this article. I have to say, it's quite handy!

Would love to see this in TypeScript!

@prabirshrestha
Copy link
Member

Any updates on this? Would love to see this in 1.6 as it would come super handy when using with React 😄

@DanielRosenwasser
Copy link
Member

@prabirshrestha for the record, we currently have the property spread operator for JSX on master.

@mnpenner
Copy link

@DanielRosenwasser Could you elaborate? Does that mean this works on master with the --jsx flag? Does that include rest properties or just spread properties?

@DanielRosenwasser
Copy link
Member

Sorry @mnpenner and @prabirshrestha, yes, I meant on master. @RyanCavanaugh knows more about this than I do, but I believe it's just spread properties.

@RyanCavanaugh
Copy link
Member

JSX supports spread attributes, e.g. <TagName {...spreadedExpr} />. Nothing to do with the ES6 spread operator other than sharing the same token and having roughly equivalent semantics.

@RichiCoder1
Copy link

I'm very interested in both Redux and React, the manipulation in state is made much easier with this. Would love to see this implemented.

@Lenne231
Copy link

Rest/Spread is approved for Stage 2 now https://github.com/tc39/tc39-notes/blob/master/es7/2015-07/july-30.md

@DanielRosenwasser DanielRosenwasser added In Discussion Not yet reached consensus and removed Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Sep 22, 2015
@RyanCavanaugh RyanCavanaugh added Revisit An issue worth coming back to and removed In Discussion Not yet reached consensus labels Oct 5, 2015
@RyanCavanaugh
Copy link
Member

We want to wait for the proposal to reach Stage 3 before addressing this.

@WanderWang
Copy link

It's very useful in react and redux , please implement it as soon as possible , please :-)

@tomduncalf
Copy link

Here's a helper module I've been using to work around this. It's not ideal as you have to specify all the keys twice, once on the left hand side and once as strings on the right hand side, but I can't think of a better way to do it. Any feedback welcome, I'm sure there's edge behaviour I've missed!

https://gist.github.com/tomduncalf/fbae862b123445c117cb

@nevir
Copy link

nevir commented Nov 18, 2015

Is this something you'd accept a patch for? (to be merged once it reaches stage 3). And if so, have any pointers of where to start making changes to support it?

This is the last ES7+ feature that my team heavily uses that's preventing us from switching over to typescript; would definitely love to have to sooner, if possible.

@mhegazy
Copy link
Contributor

mhegazy commented Nov 19, 2015

We would consider a PR here. One note though, wiring the type system for this feature is not a simple task. If you are interested in perusing this I would keep it incremental, and would start by parsing then emit then type checking. @sandersn should be able to help you if you have any questions.

@xogeny
Copy link

xogeny commented Dec 4, 2015

Just a note for those interested in using this with Redux. I agree the notation is convenient. But I've actually been getting by just fine without it by leverage the updeep library. I created some basic typings for it and it works pretty well. I even have a simple library that implements an updeep based store and actions. If anybody is interested in this, just contact me.

In any case, 👍 for spread operators in TS. 😄

@cur3n4
Copy link

cur3n4 commented Dec 13, 2015

@xogeny I would be very interested in having a look at that library. Thanks

@xogeny
Copy link

xogeny commented Dec 13, 2015

@cur3n4 I have a repository where I've been playing with these ideas. It changes quite a bit (since this is all still evolving). I was using a special modules that leveraged updeep initially and that functionality is still in the library. But I've stopped using it because I found it difficult to maintain the necessary type constraints. I suspect if Typescript had something along the lines of Java's super generic type constraints, updeep (and several other libraries, like lodash or ramda) could be made a lot more type safe.

But don't get me wrong, I still think a spread operator would be really useful. It would actually allow you to express and do things safely and tersely in the language that I don't see how to do with the current type system (i.e., allowing you to describe the right type constraints on external libraries). For the moment, I sacrifice the terseness for the safety (while the updeep approach sacrificed safety for terseness).

@amcdnl
Copy link

amcdnl commented Jan 26, 2016

After using Babel for past few months, converted my project to TS and had to go and redo all these. Would love to see this implemented!

@filipesilva
Copy link
Contributor

It seems like the proposal has reached stage 3: https://github.com/tc39/proposals

@bloadvenro
Copy link

@ddaghan your example with tsx won't work

@mhegazy mhegazy modified the milestones: TypeScript 2.1, TypeScript 2.1.2 Oct 27, 2016
@sandersn sandersn mentioned this issue Nov 3, 2016
@Zeragamba
Copy link

any timeframe for this feature?

@dsifford
Copy link
Contributor

dsifford commented Nov 5, 2016

@SpyMaster356 I've been lurking this for a while and it looks like it's close. @sandersn has been kicking ass on this for (at least) the past few weeks. 🙌

You can follow along here (#12028) and here (#11150)

@mhegazy mhegazy added the Fixed A PR has been merged for this issue label Nov 10, 2016
@xooxdoo
Copy link

xooxdoo commented Nov 12, 2016

Some one should update the Roadmap

@aaronbeall
Copy link

aaronbeall commented Dec 7, 2016

It seems that using this feature allows the assignment of unknown props:

interface State {
  a: string;
  b: number;
}

let state: State = { a: "a", b: 0 };

state = {...state, x: "x"}; // No error

I was expecting an error that x is not a known property of State. Is this not how the feature works?

For example, my current workaround before this PR was this:

state = update(state, { x: "x" }); // Error: Property 'x' is missing in type 'State'.

function update<S extends C, C>(state: S, changes: C): S {
  return Object.assign({}, state, changes);
}

Is it possible to achieve this with object spread/rest?

@mhegazy
Copy link
Contributor

mhegazy commented Dec 7, 2016

Object Rest and Spread, as per the ES proposal, behaves similar to Object.assign. the last mention of the property "wins". no errors are reported. in the example you had, the type of {...state, x:"X"} is { a: string, b:number, x:string }; and this type is assignable to State and thus the assignment works. this is the same as saying let state: State = { a: "a", b:0, x: "X" }; would be allowed as well.

@aaronbeall
Copy link

aaronbeall commented Dec 7, 2016

But that's what I'm confused about: let state: State = { a: "a", b:0, x: "X" } gives the error Object literal may only specify known properties, and 'x' does not exist in type 'State' which is what I want... why is it a valid assignment when coming out of an object literal containing a spread?

@mhegazy
Copy link
Contributor

mhegazy commented Dec 7, 2016

sorry.. object literals are a special case. my example was wrong. here is a better example:

let obj = { a: "a", b:0, x: "X" };
let state: State = obj; // OK

The issue here is if rather subjective. When the compiler sees let state:State = { a: "a", b:0, x: "X" }, chances are x is a typo, either a stale propoerty that was left off after refactoring, or a type for an optional property, that is why it is reported as an error.

however, if you spread an object, let's say let myConfig : State= { a: 1, ...myOtherBigConfigBag}, if the myOtherBigConfigBag has a few properties that you do not care about, you just need the a and the b, an error here would force you to keep these two interfaces in sync, or cast to make these errors go away.

that said. we should reconsider this decision. filed #12717 to track that.

@aaronbeall
Copy link

I see. I love your idea in #12717, that's exactly what I was thinking. I would actually like such behavior even for the spread props but I can see your point that it's very subjective and might be annoying for some common JS use cases.

@bondz
Copy link

bondz commented Dec 8, 2016

@aaronbeall I agree, it would be annoying for common JS use cases... Most of the time, you just want to ensure the object has the shape of the interface specified and not directly inspect the spread object, the current implementation is okay IMO

@olmobrutall
Copy link

Hi guys, Congrats for the great release! Now is time for a deserved rest... speacking of whitch I've an issue with the rest operator :)

Using React you tipically have a component like:

export interface MyLinkProps extends React.HTMLAttributes {
    myUrl: string
}

class MyLink{

    render(){
      const {myUrl, ...attrs } = this.props;
     return <a href={calculate(myUrl)} ...attrs>Click here</a>;
   }
}

The issue is that when you hover with the mouse over attrs you get the list of all the properties (hundreds) instead of React.HtmlAttributes.

I know that typescript is structurally typed and all that, but could be possible to assign an explicit type to the rest variable?

Some alternatives:

    const {myUrl, ...attrs as React.HtmlAttributes } = this.props; //Is not really a casting

    const {myUrl, ...attrs : React.HtmlAttributes } = this.props; //conflicts with ES6?
  
    const attrs : React.HTMLAttributes; 
    const { myUrl, ...attrs } = this.props;  //re-declare the variable

@aaronbeall
Copy link

aaronbeall commented Dec 8, 2016

@bondz Well, its not true in 100% of the uses in my project. :) But in another project it may be very annoying. In my case I'm using Redux and React and making heavy use of immutable state, which means to update or create state objects I must copy all props onto a new object literal... in all cases I want 100% type safety that I'm trying to copy the right data onto the target state interface. Currently I use functions to ensure this safety, but I would prefer to use object spread because its cleaner (readable, expressive, standard syntax.) But in someone else's project they might want different behavior because they are using a lot of untyped or loose object structures, so I see how it's quite subjective. I think the #12717 suggestion is a good middle ground (and more consistent with existing object literal type safety.)

@evisong
Copy link

evisong commented Dec 16, 2016

#2103 (comment)
We want to wait for the proposal to reach Stage 3 before addressing this.

Seems it's already state 3, any plan of supporting this?

Btw, we use VSCode mainly for ES development, not TS yet :)

@bondz
Copy link

bondz commented Dec 16, 2016

@evisong this feature has shipped and is already available in the latest version of vsCode. Update your vsCode to version 1.8

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Committed The team has roadmapped this issue ES Next New featurers for ECMAScript (a.k.a. ESNext) Fixed A PR has been merged for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.