microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.28k stars 12.39k forks source link

Support proposed ES Rest/Spread properties #2103

Closed fdecampredon closed 7 years ago

fdecampredon commented 9 years ago

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 commented 9 years ago

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

fdecampredon commented 9 years ago

@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 commented 9 years ago

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 commented 9 years ago

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

DanielRosenwasser commented 9 years ago

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

mnpenner commented 9 years ago

@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 commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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

RyanCavanaugh commented 8 years ago

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

WanderWang commented 8 years ago

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

tomduncalf commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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, :+1: for spread operators in TS. :smile:

cur3n4 commented 8 years ago

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

xogeny commented 8 years ago

@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 commented 8 years ago

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!

SergeyKorochansky commented 8 years ago

:+1:

dlebedynskyi commented 8 years ago

would love to see this.

danielfigueiredo commented 8 years ago

+1

mezzario commented 8 years ago

must have feature, please implement

lukephills commented 8 years ago

+1

baio commented 8 years ago

+1 much nicer syntax than Object.assign + JSX support

zivni commented 8 years ago

+1 it a must have for react development for passing props down (var { checked, ...other } = this.props;. see the react docs

kitsonk commented 8 years ago

it a must have for react development

Let's be honest, it is a convenience, not a must have. The proposal is at Stage 2 with TC39. The TypeScript team have made it clear, they don't want to consider implementing things that aren't originated in TypeScript until they are Stage 3. The place to "+1" is with TC39.

amcdnl commented 8 years ago

@kitsonk - you can't expect good adoption when frameworks like react / etc specifically call out these types of things in their docs. People will start using Typescript, see all these issues and go back to Babel. Thats what i did.

kitsonk commented 8 years ago

Well, I think we are talking about a broader topic. Not one browser vendor will implement anything that isn't in Stage 3 (unless they personally like it). TC39 have been trying to give some level of organisation for their part of the web. TypeScript has gotten bitten, hugely, by jumping the gun before (modules, and look at the chaos that that has caused since). I respect that they are trying to at least put some guidance and structure around what they are willing to implement and when. Is there some other suggestion of what standard should be used? I don't think "because X framework mentions it in their docs" would be a good one.

frameworks like react / etc specifically call out these types of things in their docs

Which they mark as only a proposal and then go on to instruct you how to jump through hoops to configure Babel 6 to use it. I would rather blame frameworks for depending on syntactical technologies that are only at a draft specification. Personally, we in Dojo, got burned by Object.observe and I am reticent again to try to depend on technologies before they get to Stage 3 in Dojo.

The particular path is a lot more than just supporting transforming the syntax on emit for TypeScript. There are all the type inference that needs to follow with it. I have looked at the draft spec to understand, but where do symbols go? Where do non-enumerable properties go? I assume in both cases they just disappear into the ether, but there will be all sorts of type splitting and merging that will need to go on in the background for this, so I can totally understand that TypeScript team saying "ok, let's hold off on this until TC39 has really kicked the tires on this".

jods4 commented 8 years ago

Overall I think that TS is quite well featured today. There was a time (around 1.4 ~ 1.5 I think) where I was frustrated by lack of some ES2015 features, but as of today TS has catched up pretty well with the standard.

Of course, there is sometimes feature envy wrt other languages. For instance, Babel offers pretty much any JS proposal, including "experimental" stuff. Personally, I am looking forward to this issue and the function bind operator as well.

But at the same time we have to acknowledge that the TS team takes compatibility a lot more seriously than Babel does. They don't want to change the semantics of your code between releases, or simply remove a feature, which is what Babel does. For some (enterprise?) projects that's important.

I can also understand that MS doesn't want to invest a lot of resources into features that may get dropped, the Object.observe fiasco comes to mind. Actually, they said they would consider a PR on this one for instance :wink:.

In the past some features were added early behind an experimental compiler switch (e.g. async), which meant you had to opt in and acknowledge that the feature might change or get removed in future releases. Maybe that could be done again for the most popular requests?

mhegazy commented 8 years ago

Just a quick note here. We have been rather slow in adopting stage 0-1 proposed ES features mainly due to backward compatibility concerns. We do follow the discussions in the TC39 meetings closely, and participate directly in the meetings or in proposal preparations before the meetings; and when we put a feature in we would like to know what that means to users who take a dependency on them. If you look at the TC39 process documentation a feature at stage 1 can expect "Major" post acceptance changes. This was the cases for classes, modules, iterators, and almost all non-trivial ES6 features.. Putting my user hat on, breaking changes to syntax can be mechanical to respond to, whereas breaking changes to semantics can be extremely hard to catch and fix; this can be a huge cost on teams using any of these features, and/or a blocker for adopting newer versions of the tools. and we would like to minimize that when possible. The is why we have adopted a policy to enable features by default when they reach stage 3, and under an experimental flag before that (e.g. decorators). Having said that, the object property spread and rest are on our roadmap, and we do plan to tackle them in a future release.

baio commented 8 years ago

Maybe it is worth consider to add to compiler ability to inject some 3rd party extensions, as done in babel ?

nevir commented 8 years ago

Another thought: what about support for preprocessing typescript sources (with Babel)?

If we can get Babel (or whatever library) to expand object spreads into Object.assign calls, that might work well enough? It'd hopefully be generalizable to other experimental features implemented in Babel, too.

amcdnl commented 8 years ago

@kitsonk - broader topic absolutely, dang wish github had better discussions ;). I disagree w/ you quite a bit tho:

. TypeScript has gotten bitten, hugely, by jumping the gun before (modules, and look at the chaos that that has caused since).

They went off and did their own implementation mirroring .NET not using anything from the community, thats their own fault.

put some guidance and structure around what they are willing to implement and when

they implement decorators ( stage 1 ), class properties ( stage 1 ), etc.

I don't think "because X framework mentions it in their docs" would be a good one.

your kidding right? react is the most popular framework and has a projection to stay that way for awhile now. not supporting that will/has really hurt adoption.

I think TypeScript has a lot of catching up to do with Babel and its got a lot of challenges such as: really difficult to start using it with an existing app, migrating from babel to typescript for A2 is a NIGHTMARE!, lack of decoupled plugins makes working w/ Node extremely difficult.

kitsonk commented 8 years ago

your kidding right? react is the most popular framework and has a projection to stay that way for awhile now. not supporting that will/has really hurt adoption.

I have a lot of respect for React, but I am not kidding. It isn't the only game in town and it is certainly on a large hype curve. 6 months ago it was React + Flux, now React + Redux is the flavour of the day. A year ago, Angular and Polymer. Two years ago it was Backbone and Ember. Six years ago it was Dojo, MooTools, jQuery, gwt, ExtJS, etc...

baio commented 8 years ago

don't start framework wars here :exclamation: {..., } syntax is much handsome as is, no matter any framewrok

aluanhaddad commented 8 years ago

They went off and did their own implementation mirroring .NET not using anything from the community, thats their own fault.

@amcdnl What exactly are you referring to? I can only assume you are referring to the module as namespace issue, which has nothing to do with .NET and is also not one of the major pain points with modules in TypeScript.

afaayerhan commented 8 years ago

+1 for this to be included in typescript node js is supporting it exprimentally

PhilipDavis commented 8 years ago

let typescript = { ...typescript, object_spread }; // Yes please.

atticoos commented 8 years ago

Bummer there's no movement on this, I really hope this reaches stage 3 soon. Going to be killing syntax error highlighting in VSCode if possible

jods4 commented 8 years ago

I wonder if Salsa changes anything to the game?

I understand that TS doesn't want to implement unstable features early: limited resources, shielding us users from possible breaking changes, etc.

But now that Salsa is VS Code default JS engine, the pressure for newer JS syntax (at least just the syntax, not full TS typing) is going to increase. Especially given the popularity of Babel.

Is Salsa going to be able to accept a broader syntax faster than TS?

stepancar commented 8 years ago

+1

asvetliakov commented 8 years ago

Is this going to be in 2.1 rather than 2.0? :cry: https://github.com/Microsoft/TypeScript/wiki/Roadmap#21

RichiCoder1 commented 8 years ago

Considering how large 2.0 is already turning out to be, I'm not too suprised.

mhegazy commented 8 years ago

We try to keep the releases 6-8 weeks apart; so that limits what can go in. we are doing some emitter refactoring at the moment, and should be able to add this feature once that is completed.

nerijusgood commented 8 years ago

So is this fixed?

...standalone helper function for spread attributes https://github.com/Microsoft/TypeScript/releases/tag/v1.8.10

Since I am still getting "Property restructuring pattern expected"

dallonf commented 8 years ago

Not exactly. That fix is specifically for JSX spread attributes:

const props = { foo: "bar" };
return <SomeComponent {...props} />;

which already worked, before React v15 removed an undocumented internal function that TypeScript's JSX compiler depended on. Oops 😃

Object spread is a different proposal, with an admittedly similar syntax (it may have even been proposed by Facebook engineers and inspired by the JSX equivalent, although I don't know for sure).

dgsunesen commented 8 years ago

So since {...props} does not yet work, how would you using TypeScript achieve something similar to that?

basarat commented 8 years ago

So since {...props} does not yet work, how would you using TypeScript achieve something similar to that

Use xtend : https://www.npmjs.com/package/xtend it has great typings : https://github.com/typed-typings/npm-xtend (by @blakeembrey :rose:) thanks to the intersection type

dallonf commented 8 years ago

Or don't use any library at all!

const newProps = Object.assign({} /*new object*/, props /* add all attributes of props */, {
   // add additional props
  bar: "baz"
});

It's a bit verbose, but it is native to ES2015, so if you've already got a polyfill for that, you're solid.