-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Destructuring (ES6) #240
Comments
Paging @danquirk for proposal |
Work In Progress: Destructuring Homogeneous ArraysFor cases with homogeneous arrays destructuring is quite simple and the type system can treat the results essentially as the equivalent desugared form. // Example
function foo { return [1, 2]; }
var [x,y] = foo();
// Codegen
var __a = foo(); // fresh var __a
var x = __a[0];
var y = __a[1]; More examples of this sort will follow at the end. Destructuring Hetergeneous Arrays as TuplesThis is the more interesting and complicated set of cases for destructuring. How do we make this work nicely? How do we surface any proposal into a .d.ts? function returnMultipleValues { return [1, "a"]; } // return type is {}[]
var [x,y] = returnMultipleValues(); If x and y are treated just as the desugared form then x and y will have the type Proposal: Arrays with Element Type Informationvar xs = [1, "a"];
// type is {}[number, string]
// equivalent to {}[number, string, {}. {}. ...] Leads to: var xs = returnMultipleValues();
var x = xs[0]; // number
var y = xs[1]; // string
var z = xs[2]; // {}, would be nice to be able to give an error But these would not check element types and still be allowed (as it is today): xs = ["oh", "no"]; // assign string[] to {}[]
xs = []; Issues var xs = returnMultipleValues(); // returns {}[number, string]
var i = 0;
var x = xs[i]; // {} unless you have data flow analysis support
var y = xs[0]; // number
x = y; // ok
y = x; // error
xs[0] = 2; // error, BREAKING CHANGE Possible Solution: Only use array element types when processing destructuring patterns, never at other times. Proposal: Syntax for Tuple TypesPros:
Cons
ExamplesSome examples from http://fitzgeraldnick.com/weblog/50/
/* Example 1 */
var xs = [1, 2];
var [x, y] = xs;
// ES5 Codegen
var __xs = xs; // fresh var
var x = __xs[0];
var y = __xs[1];
/* Example 2 */
// nested destructuring
var [foo, [[bar], baz]] = [1, [[2], 3]];
// ES5 Codegen
var __xs = [1, [[2], 3]];
var foo = __xs[0];
var bar = __xs[1][0];
var baz = __xs[1][1];
/* Example 3 */
// skipping elements
var [, , third] = ["foo", "bar", "baz"];
// ES5 Codegen
var __xs = ["foo", "bar", "baz"];
var third = __xs[2];
/* Example 4 */
// using rest pattern
var [head, ...tail] = [1, 2, 3, 4];
// ES5 Codegen
var __xs = [1, 2, 3, 4];
var head = __xs[0];
// TODO: too slow?
var tail = __xs.slice(1, __xs.length - 1);
/* Example 5 */
// destructuring objects
var robotA = { name: "Bender" };
var robotB = { name: "Flexo" };
var { name: nameA } = robotA;
var { name: nameB } = robotB;
// ES5 Codegen
var __robotA = { name: "Bender" }; // fresh vars
var __robotB = { name: "Flexo" };
var nameA = __robotA.name;
var nameB = __robotB.name;
/* Example 6 */
// property and var names the same
var { foo, bar } = { foo: "lorem", bar: "ipsum" };
// ES5 Codegen
var __a = { foo: "lorem", bar: "ipsum" };
var foo = __a.foo;
var bar = __a.bar;
/* Example 7 */
// complicated object destructuring
var complicatedObj = {
arrayProp: [
"Zapp",
{ second: "Brannigan" }
]
};
var { arrayProp: [first, { second }] } = complicatedObj;
//ES5 Codegen
var __b = complicatedObj;
var arrayProp = __b.arrayProp;
var first = arrayProp[0];
var second = arrayProp[1].second;
/* Example 8 */
// default values
var [missing = true] = [];
// ES5 codegen
var __c = [];
var missing = __c ? __c : true;
/* Example 9 */
// destructuring in function params
function removeBreakpoint({ url, line, column }) { }
// ES5 codegen
function removeBreakpoint(__a) {
var url = __a.url;
var line = __a.url;
var column = __a.url;
}
/* Example 10 */
// for loop var
for (let[key, value] of map) {
console.log(key + " is " + value);
}
// ES5 codegen
for(let __a of map) {
let key = __a.key;
let value = __a.key;
}
/* Example 11 */
function returnMultipleValues() {
return [1, 2];
}
var [foo, bar] = returnMultipleValues();
// ES5 codegen
// multiple function return values in array
var __a = returnMultipleValues(); // fresh var
var foo = __a[0];
var bar = __a[1];
/* Example 12 */
function returnMultipleValues2() {
return { x: 1, y: 2 }
}
var [foo, bar] = returnMultipleValues2();
// ES5 codegen
// multiple function return values in array
var __a = returnMultipleValues(); // fresh var
var foo = __a.foo;
var bar = __a.bar;
/* Example 13 */
// assignment only destructuring
({ safe }) = {};
// ES5 codegen
var __a = {};
safe = __a;
/* Example 14 */
// module destructuring
const { SourceMapConsumer, SourceNode } = require("source-map");
// TODO: larger item with modules, maybe not even allowed anymore?
/* Example 15 */
interface I {
foo: string;
bar: number;
}
var i: I = { foo: '', bar: 2 }
var { foo, bar } = i;
// ES5 Codegen
var __a = i;
var foo = __a.foo;
var bar = __a.bar; |
Is example 8 supposed to use the ternary operator's truthiness or is it supposed to check specifically for undefined / hasOwnProperty()? Granted, I can't find anything in the harmony proposal nor MDN, and while FF Nightly does support destructuring assignment it doesn't support default values. |
See also #428 |
👍 |
+1 |
For syntax of type annotations I would prefer the first option : var {x: myX: number, y: myY: number} = myPoint(); // with renaming
var {x:: number, y:: number} = myPoint(); // without renaming than having over verbose syntax that force us to repeat the var names, especially for function arguments destructuring. function myFunc({x::number, y::number }) {
} Seems a lot easier to read for me than : function myFunc({x, y}: {x: number; y: number}) {
} |
Test required : does it work with import {each, map} = require('underscore'); Asked on SO: http://stackoverflow.com/questions/27453272/destructuring-external-modules-in-typescript/27465804#27465804 |
Per the ES6 spec, example 8 should be as follows: /* Example 8 */
// default values
var [missing = true] = [];
// ES5 codegen
var __c = [];
var __c0 = __c[0];
var missing = (__c0 === undefined) ? __c0 : true; Note that __c[0] is to be accessed only once and thus has to be lifted to a temporary, and that the default value is assigned only if the actual value is undefined. Example 2 also has the same problem of accessing xs[1] more than once. Example 7 gets it right. Edit: I see Ander's PR does this correctly already. Lastly, the spec defines destructuring in terms of iterators, so: var arr = [1, 2];
arr[Symbol.iterator] = function* () { yield "5"; }
arr[0]; // Still 1
arr[1]; // Still 2
var [foo, bar] = arr; // foo = "5", bar = undefined The ES5 codegen that simply indexes into the array will give wrong results. TS can't really be expected to cope with this. This is only a problem for the ES5 codegen and one can say that Iterators aren't in ES5, so this is a caveat the user must be aware of if they choose to run the ES5 codegen in an environment that supports Iterators. Even in ES6 codegen that leaves the destructuring assignment syntax unchanged, this is still a problem for the typechecker - it will infer foo and bar to be numbers but the actual iterator returns a string and undefined. To prevent this, the typechecker should validate that the |
@ahejlsberg - can you look at the above comment? |
@RyanCavanaugh @Arnavion I will be updating the spec with the formal typing rules for destructuring, spread, and rest as soon as I can get to it, but the current implementation in master is to the best of my knowledge correct and complete with respect to type checking and code generation. The one area still left to do is integration with iterators. As you point out above this is relevant only when the target is ES6. |
The integration with iterators is not relevant only when the target is ES6, it's relevant when the runtime supports Iterators. My point was that the ES5 codegen is doing the wrong thing if run on an environment that supports iterators. It's not something that TS can fix easily - it can't know that the runtime is going to support iterators and generate the correct codegen. I'm just asking that the spec / documentation for destructuring include the note that the ES5 codegen is not in line with ES6 semantics (unlike, say, classes, getters, arrow functions and the other downlevel emits which are in line). The reason I'm talking about this situation of running ES5 codegen on partially ES6 runtimes is that this is the situation we will have today. I doubt anyone is going to compile with |
I'm not super concerned with scenarios where users replace an array's iterator with one that doesn't actually enumerate the elements of the array. You're right that our destructuring code generation for ES5 is an approximation of ES6 behavior, but it is a very faithful and accurate approximation for well behaved programs, and I wouldn't say it is doing the wrong thing if run in an environment that supports iterators. Rather, I'd say it doesn't give you access to the additional features offered by iterators. I think this is very much in line with the way our downlevel code generation for other ES6 features is designed (and there are certainly esoteric corners where classes and arrow functions likewise differ from native ES6). But perhaps I'm missing your point here... |
I'm only asking for the docs / spec to mention this particular caveat. I agree that for programs that don't do funky things with Array.prototype's Iterator there will be no difference, so I can understand your point that the ES5 codegen isn't "wrong" so much as an approximation. I just think it's a reasonable assumption for TS users to make that if an ES6 feature has a ES5 emit, then the ES5 emit has the same semantics as the ES6 emit unless documented otherwise. For example, accessors don't have an ES3 emit because it's impossible to have them there with the same semantics. If you're saying that this is not true of even arrow functions and classes, then clearly this assumption is wrong, but I'm not aware of any such differences (inheritance converting static accessors to properties is one difference but it's well-known). Modules are also different from ES6 and again that is well-documented. |
With #1878 the spec now documents our implementation of destructuring, modulo support for iterators. Keeping this bug open to track that. |
Created #1900 to track work on iterators so we can close this issue. |
Are there any plans to add type annotation to destructured variables? |
@mpawelski It's a bit verbose to avoid clashing with renaming syntax but it's possible: var a: any;
var [x, y]: [string, number] = a; // x is string, y is number
var { w, z }: { w: string, z: number } = a; // w is string, z is number |
@Arnavion well, I know you can do that, but you need to write each property name twice which is far from ideal, and the purpose of descructuring is just to make code more succinct and easier to read/write, it's just a syntatic sugar. If I need to write each property name twice then it's better for me to not use destructuring at all. I really like @fdecampredon proposition
and wonder if Typescript team plan on adding something similar |
Example
Codegen
Type annotations
The text was updated successfully, but these errors were encountered: