Skip to content

Commit 214ce38

Browse files
committed
Downlevel array destructuring to ES6 in object rest
Previously array destructuring inside an object destructuring with an object rest would downlevel the array destructuring to ES5. This breaks if the code that targets ES2015 is using iterators instead of arrays since iterators don't support [0] or .slice that the ES5 emit uses.
1 parent 9977936 commit 214ce38

File tree

2 files changed

+110
-34
lines changed

2 files changed

+110
-34
lines changed

src/compiler/factory.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3568,8 +3568,7 @@ namespace ts {
35683568
setEmitFlags(body, EmitFlags.NoSourceMap | EmitFlags.NoTokenSourceMaps);
35693569

35703570
let forStatement: ForStatement | ForOfStatement;
3571-
if(convertObjectRest) {
3572-
3571+
if (convertObjectRest) {
35733572
forStatement = createForOf(
35743573
createVariableDeclarationList([
35753574
createVariableDeclaration(rhsReference, /*type*/ undefined, /*initializer*/ undefined, /*location*/ node.expression)

src/compiler/transformers/destructuring.ts

Lines changed: 109 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ namespace ts {
5252
location = value;
5353
}
5454

55-
flattenDestructuring(node, value, location, emitAssignment, emitTempVariableAssignment, emitRestAssignment, transformRest, visitor);
55+
flattenDestructuring(node, value, location, emitAssignment, emitTempVariableAssignment, recordTempVariable, emitRestAssignment, transformRest, visitor);
5656

5757
if (needsValue) {
5858
expressions.push(value);
@@ -98,7 +98,7 @@ namespace ts {
9898
transformRest?: boolean) {
9999
const declarations: VariableDeclaration[] = [];
100100

101-
flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, emitRestAssignment, transformRest, visitor);
101+
flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, noop, emitRestAssignment, transformRest, visitor);
102102

103103
return declarations;
104104

@@ -140,7 +140,7 @@ namespace ts {
140140
const declarations: VariableDeclaration[] = [];
141141

142142
let pendingAssignments: Expression[];
143-
flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, emitRestAssignment, transformRest, visitor);
143+
flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, recordTempVariable, emitRestAssignment, transformRest, visitor);
144144

145145
return declarations;
146146

@@ -201,7 +201,7 @@ namespace ts {
201201

202202
const pendingAssignments: Expression[] = [];
203203

204-
flattenDestructuring(node, /*value*/ undefined, node, emitAssignment, emitTempVariableAssignment, emitRestAssignment, /*transformRest*/ false, visitor);
204+
flattenDestructuring(node, /*value*/ undefined, node, emitAssignment, emitTempVariableAssignment, noop, emitRestAssignment, /*transformRest*/ false, visitor);
205205

206206
const expression = inlineExpressions(pendingAssignments);
207207
aggregateTransformFlags(expression);
@@ -244,6 +244,7 @@ namespace ts {
244244
location: TextRange,
245245
emitAssignment: (name: Identifier, value: Expression, location: TextRange, original: Node) => void,
246246
emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier,
247+
recordTempVariable: (node: Identifier) => void,
247248
emitRestAssignment: (elements: (ObjectLiteralElementLike[] | BindingElement[]), value: Expression, location: TextRange, original: Node) => void,
248249
transformRest: boolean,
249250
visitor?: (node: Node) => VisitResult<Node>) {
@@ -307,48 +308,91 @@ namespace ts {
307308
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment);
308309
}
309310

310-
let es2015: ObjectLiteralElementLike[] = [];
311+
let bindingElements: ObjectLiteralElementLike[] = [];
311312
for (let i = 0; i < properties.length; i++) {
312313
const p = properties[i];
313314
if (p.kind === SyntaxKind.PropertyAssignment || p.kind === SyntaxKind.ShorthandPropertyAssignment) {
314-
if (transformRest && !(p.transformFlags & TransformFlags.ContainsSpreadExpression)) {
315-
es2015.push(p);
316-
}
317-
else {
318-
if (es2015.length) {
319-
emitRestAssignment(es2015, value, location, target);
320-
es2015 = [];
315+
if (!transformRest ||
316+
p.transformFlags & TransformFlags.ContainsSpreadExpression ||
317+
(p.kind === SyntaxKind.PropertyAssignment && p.initializer.transformFlags & TransformFlags.ContainsSpreadExpression)) {
318+
if (bindingElements.length) {
319+
emitRestAssignment(bindingElements, value, location, target);
320+
bindingElements = [];
321321
}
322322
const propName = <Identifier | LiteralExpression>(<PropertyAssignment>p).name;
323323
const bindingTarget = p.kind === SyntaxKind.ShorthandPropertyAssignment ? <ShorthandPropertyAssignment>p : (<PropertyAssignment>p).initializer || propName;
324324
// Assignment for bindingTarget = value.propName should highlight whole property, hence use p as source map node
325325
emitDestructuringAssignment(bindingTarget, createDestructuringPropertyAccess(value, propName), p);
326326
}
327+
else {
328+
bindingElements.push(p);
329+
}
327330
}
328331
else if (i === properties.length - 1 && p.kind === SyntaxKind.SpreadAssignment) {
329332
Debug.assert((p as SpreadAssignment).expression.kind === SyntaxKind.Identifier);
330-
if (es2015.length) {
331-
emitRestAssignment(es2015, value, location, target);
332-
es2015 = [];
333+
if (bindingElements.length) {
334+
emitRestAssignment(bindingElements, value, location, target);
335+
bindingElements = [];
333336
}
334337
const propName = (p as SpreadAssignment).expression as Identifier;
335338
const restCall = createRestCall(value, target.properties, p => p.name, target);
336339
emitDestructuringAssignment(propName, restCall, p);
337340
}
338341
}
339-
if (es2015.length) {
340-
emitRestAssignment(es2015, value, location, target);
341-
es2015 = [];
342+
if (bindingElements.length) {
343+
emitRestAssignment(bindingElements, value, location, target);
344+
bindingElements = [];
342345
}
343346
}
344347

345348
function emitArrayLiteralAssignment(target: ArrayLiteralExpression, value: Expression, location: TextRange) {
349+
if (transformRest) {
350+
emitESNextArrayLiteralAssignment(target, value, location);
351+
}
352+
else {
353+
emitES2015ArrayLiteralAssignment(target, value, location);
354+
}
355+
}
356+
357+
function emitESNextArrayLiteralAssignment(target: ArrayLiteralExpression, value: Expression, location: TextRange) {
346358
const elements = target.elements;
347359
const numElements = elements.length;
348360
if (numElements !== 1) {
349361
// For anything but a single element destructuring we need to generate a temporary
350362
// to ensure value is evaluated exactly once.
351-
// When doing so we want to hightlight the passed in source map node since thats the one needing this temp assignment
363+
// When doing so we want to highlight the passed-in source map node since thats the one needing this temp assignment
364+
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment);
365+
}
366+
367+
const expressions: Expression[] = [];
368+
const spreadContainingExpressions: [Expression, Identifier][] = [];
369+
for (let i = 0; i < numElements; i++) {
370+
const e = elements[i];
371+
if (e.kind === SyntaxKind.OmittedExpression) {
372+
continue;
373+
}
374+
if (e.transformFlags & TransformFlags.ContainsSpreadExpression && i < numElements - 1) {
375+
const tmp = createTempVariable(recordTempVariable);
376+
spreadContainingExpressions.push([e, tmp]);
377+
expressions.push(tmp);
378+
}
379+
else {
380+
expressions.push(e);
381+
}
382+
}
383+
emitAssignment(updateArrayLiteral(target, expressions) as any as Identifier, value, undefined, undefined);
384+
for (const [e, tmp] of spreadContainingExpressions) {
385+
emitDestructuringAssignment(e, tmp, e);
386+
}
387+
}
388+
389+
function emitES2015ArrayLiteralAssignment(target: ArrayLiteralExpression, value: Expression, location: TextRange) {
390+
const elements = target.elements;
391+
const numElements = elements.length;
392+
if (numElements !== 1) {
393+
// For anything but a single element destructuring we need to generate a temporary
394+
// to ensure value is evaluated exactly once.
395+
// When doing so we want to highlight the passed-in source map node since thats the one needing this temp assignment
352396
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment);
353397
}
354398

@@ -413,15 +457,24 @@ namespace ts {
413457
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ numElements !== 0, target, emitTempVariableAssignment);
414458
}
415459
if (name.kind === SyntaxKind.ArrayBindingPattern) {
416-
emitArrayBindingElement(name, value);
460+
emitArrayBindingElement(name as ArrayBindingPattern, value);
417461
}
418462
else {
419463
emitObjectBindingElement(target, value);
420464
}
421465
}
422466
}
423467

424-
function emitArrayBindingElement(name: BindingPattern, value: Expression) {
468+
function emitArrayBindingElement(name: ArrayBindingPattern, value: Expression) {
469+
if (transformRest) {
470+
emitESNextArrayBindingElement(name, value);
471+
}
472+
else {
473+
emitES2015ArrayBindingElement(name, value);
474+
}
475+
}
476+
477+
function emitES2015ArrayBindingElement(name: ArrayBindingPattern, value: Expression) {
425478
const elements = name.elements;
426479
const numElements = elements.length;
427480
for (let i = 0; i < numElements; i++) {
@@ -439,20 +492,44 @@ namespace ts {
439492
}
440493
}
441494

495+
function emitESNextArrayBindingElement(name: ArrayBindingPattern, value: Expression) {
496+
const elements = name.elements;
497+
const numElements = elements.length;
498+
const bindingElements: BindingElement[] = [];
499+
const spreadContainingElements: BindingElement[] = [];
500+
for (let i = 0; i < numElements; i++) {
501+
const element = elements[i];
502+
if (isOmittedExpression(element)) {
503+
continue;
504+
}
505+
if (element.transformFlags & TransformFlags.ContainsSpreadExpression && i < numElements - 1) {
506+
spreadContainingElements.push(element);
507+
bindingElements.push(createBindingElement(undefined, undefined, getGeneratedNameForNode(element), undefined, value));
508+
}
509+
else {
510+
bindingElements.push(element);
511+
}
512+
}
513+
emitAssignment(updateArrayBindingPattern(name, bindingElements) as any as Identifier, value, undefined, undefined);
514+
for (const element of spreadContainingElements) {
515+
emitBindingElement(element, getGeneratedNameForNode(element));
516+
}
517+
}
518+
442519
function emitObjectBindingElement(target: VariableDeclaration | ParameterDeclaration | BindingElement, value: Expression) {
443520
const name = target.name as BindingPattern;
444521
const elements = name.elements;
445522
const numElements = elements.length;
446-
let es2015: BindingElement[] = [];
523+
let bindingElements: BindingElement[] = [];
447524
for (let i = 0; i < numElements; i++) {
448525
const element = elements[i];
449526
if (isOmittedExpression(element)) {
450527
continue;
451528
}
452529
if (i === numElements - 1 && element.dotDotDotToken) {
453-
if (es2015.length) {
454-
emitRestAssignment(es2015, value, target, target);
455-
es2015 = [];
530+
if (bindingElements.length) {
531+
emitRestAssignment(bindingElements, value, target, target);
532+
bindingElements = [];
456533
}
457534
const restCall = createRestCall(value,
458535
name.elements,
@@ -462,21 +539,21 @@ namespace ts {
462539
}
463540
else if (transformRest && !(element.transformFlags & TransformFlags.ContainsSpreadExpression)) {
464541
// do not emit until we have a complete bundle of ES2015 syntax
465-
es2015.push(element);
542+
bindingElements.push(element);
466543
}
467544
else {
468-
if (es2015.length) {
469-
emitRestAssignment(es2015, value, target, target);
470-
es2015 = [];
545+
if (bindingElements.length) {
546+
emitRestAssignment(bindingElements, value, target, target);
547+
bindingElements = [];
471548
}
472549
// Rewrite element to a declaration with an initializer that fetches property
473550
const propName = element.propertyName || <Identifier>element.name;
474551
emitBindingElement(element, createDestructuringPropertyAccess(value, propName));
475552
}
476553
}
477-
if (es2015.length) {
478-
emitRestAssignment(es2015, value, target, target);
479-
es2015 = [];
554+
if (bindingElements.length) {
555+
emitRestAssignment(bindingElements, value, target, target);
556+
bindingElements = [];
480557
}
481558
}
482559

0 commit comments

Comments
 (0)