Repository navigation
Destructuring (ES6) #240
Description
Activity
RyanCavanaugh commented
on Jul 24, 2014 MemberAuthorMore actionsPaging Dan Quirk (@danquirk) for proposal
Work In Progress:
Destructuring Homogeneous Arrays
For 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 Tuples
This 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
{}which is not very useful. We would need arrays to carry some additional type information about their individual element types in a way that still behaves in a backwards compatible fashion.Proposal: Arrays with Element Type Information
var 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 Types
Pros:
- Can give static errors for accessing outside the bounds of the tuple instead of behaving like an array and returning {} for those additional elements
- Explicit, distinct from arrays
Cons
- Doesn't play nicely with existing JavaScript
Examples
Some examples from http://fitzgeraldnick.com/weblog/50/
- how do type annotations work in LHS of destructuring assignment? (see initial post for 2 options)
- interface/class instances are just destructured like an object pattern?
- do default values work with object pattern? should be basically the same as array pattern
- is destructuring allowed in function param position? if not can we just do it anyway?
- module import destructuring allowed?
/* 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.
RyanCavanaugh commented
on Aug 15, 2014 MemberAuthorMore actionsSee also #428
👍
+1
fdecampredon commented
on Oct 20, 2014 More actionsFor 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}) { }
Reacted by yunji and Long LazuliTest required : does it work with
import: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
Array<T>[Symbol.iterator]method only yields values of type T if overridden.- addedCommittedThe team has roadmapped this issueThe team has roadmapped this issueand removedNeeds ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.This issue needs a plan that clarifies the finer details of how it could be implemented.
on Dec 17, 2014 1 remaining item
Ryan Cavanaugh (@RyanCavanaugh) Arnav Singh (@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.
Anders Hejlsberg (@ahejlsberg)
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
-target ES6. Even ignoring the fact that a lot of runtimes out there are ES5 or below (old browsers, node stable), no runtime completely supports ES6 either. Even when talking about a single feature, array literal destructuring, no browser supports the default value syntaxvar [missing = true] = [];. So if users do start using ES6 features in their TS codebase, they'll probably continue to use-target ES5. That's why the situation of ES5 codegen running on runtimes that partially support ES6 is one to consider.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.
- addedSpecIssues related to the TypeScript language specificationIssues related to the TypeScript language specificationand removedCommittedThe team has roadmapped this issueThe team has roadmapped this issueSuggestionAn idea for TypeScriptAn idea for TypeScript
on Feb 3, 2015 Created #1900 to track work on iterators so we can close this issue.
- removedSpecIssues related to the TypeScript language specificationIssues related to the TypeScript language specification
on Feb 3, 2015 Are there any plans to add type annotation to destructured variables?
I see there were some discussion in this thread.
I would be very useful for example for React stateless functions components.
Flow team is thinking about it to
facebook/flow#235Mariusz Pawelski (@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
Reacted by Kir GArnav Singh (@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 François de Campredon (@fdecampredon) proposition
var {x: myX: number, y: myY: number} = myPoint(); // with renaming var {x:: number, y:: number} = myPoint(); // without renamingand wonder if Typescript team plan on adding something similar
Reacted by ragtime, johnhwright, Naru, Mikki Kobvel, Nik Karbaum, Jakub Sedlacek, mavericken, Shahar "Dawn" Or, Clara Attermo and David- locked and limited conversation to collaborators
on Jun 18, 2018
Example
Codegen
Type annotations