Skip to content

Commit e3a08ed

Browse files
committed
Downlevel emit of rest elements
1 parent a7c1836 commit e3a08ed

3 files changed

Lines changed: 596 additions & 40 deletions

File tree

src/compiler/emitter.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ var __assign = (this && this.__assign) || Object.assign || function(t) {
4242
return t;
4343
};`;
4444

45+
const restHelper = `
46+
var __rest = (this && this.__rest) || function (s, e) {
47+
var t = {};
48+
49+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && !e.indexOf(p))
50+
t[p] = s[p];
51+
return t;
52+
};`;
53+
4554
// emit output for the __decorate helper function
4655
const decorateHelper = `
4756
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
@@ -226,6 +235,7 @@ const _super = (function (geti, seti) {
226235
let currentFileIdentifiers: Map<string>;
227236
let extendsEmitted: boolean;
228237
let assignEmitted: boolean;
238+
let restEmitted: boolean;
229239
let decorateEmitted: boolean;
230240
let paramEmitted: boolean;
231241
let awaiterEmitted: boolean;
@@ -2210,6 +2220,11 @@ const _super = (function (geti, seti) {
22102220
assignEmitted = true;
22112221
}
22122222

2223+
if (!restEmitted && node.flags & NodeFlags.HasRestAttribute) {
2224+
writeLines(restHelper);
2225+
restEmitted = true;
2226+
}
2227+
22132228
if (!decorateEmitted && node.flags & NodeFlags.HasDecorators) {
22142229
writeLines(decorateHelper);
22152230
if (compilerOptions.emitDecoratorMetadata) {

src/compiler/transformers/destructuring.ts

Lines changed: 153 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ namespace ts {
1717
node: BinaryExpression,
1818
needsValue: boolean,
1919
recordTempVariable: (node: Identifier) => void,
20-
visitor?: (node: Node) => VisitResult<Node>): Expression {
20+
visitor?: (node: Node) => VisitResult<Node>,
21+
transformRest?: boolean): Expression {
2122

2223
if (isEmptyObjectLiteralOrArrayLiteral(node.left)) {
2324
const right = node.right;
@@ -51,7 +52,7 @@ namespace ts {
5152
location = value;
5253
}
5354

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

5657
if (needsValue) {
5758
expressions.push(value);
@@ -61,7 +62,7 @@ namespace ts {
6162
aggregateTransformFlags(expression);
6263
return expression;
6364

64-
function emitAssignment(name: Identifier, value: Expression, location: TextRange) {
65+
function emitAssignment(name: Identifier | ObjectLiteralExpression, value: Expression, location: TextRange) {
6566
const expression = createAssignment(name, value, location);
6667

6768
// NOTE: this completely disables source maps, but aligns with the behavior of
@@ -77,6 +78,10 @@ namespace ts {
7778
emitAssignment(name, value, location);
7879
return name;
7980
}
81+
82+
function emitRestAssignment(elements: ObjectLiteralElementLike[], value: Expression, location: TextRange) {
83+
emitAssignment(createObjectLiteral(elements), value, location);
84+
}
8085
}
8186

8287
/**
@@ -89,14 +94,15 @@ namespace ts {
8994
export function flattenParameterDestructuring(
9095
node: ParameterDeclaration,
9196
value: Expression,
92-
visitor?: (node: Node) => VisitResult<Node>) {
97+
visitor?: (node: Node) => VisitResult<Node>,
98+
transformRest?: boolean) {
9399
const declarations: VariableDeclaration[] = [];
94100

95-
flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, visitor);
101+
flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, emitRestAssignment, transformRest, visitor);
96102

97103
return declarations;
98104

99-
function emitAssignment(name: Identifier, value: Expression, location: TextRange) {
105+
function emitAssignment(name: Identifier | BindingPattern, value: Expression, location: TextRange) {
100106
const declaration = createVariableDeclaration(name, /*type*/ undefined, value, location);
101107

102108
// NOTE: this completely disables source maps, but aligns with the behavior of
@@ -112,6 +118,10 @@ namespace ts {
112118
emitAssignment(name, value, location);
113119
return name;
114120
}
121+
122+
function emitRestAssignment(elements: BindingElement[], value: Expression, location: TextRange) {
123+
emitAssignment(createObjectBindingPattern(elements), value, location);
124+
}
115125
}
116126

117127
/**
@@ -125,15 +135,16 @@ namespace ts {
125135
node: VariableDeclaration,
126136
value?: Expression,
127137
visitor?: (node: Node) => VisitResult<Node>,
128-
recordTempVariable?: (node: Identifier) => void) {
138+
recordTempVariable?: (node: Identifier) => void,
139+
transformRest?: boolean) {
129140
const declarations: VariableDeclaration[] = [];
130141

131142
let pendingAssignments: Expression[];
132-
flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, visitor);
143+
flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, emitRestAssignment, transformRest, visitor);
133144

134145
return declarations;
135146

136-
function emitAssignment(name: Identifier, value: Expression, location: TextRange, original: Node) {
147+
function emitAssignment(name: Identifier | BindingPattern, value: Expression, location: TextRange, original: Node) {
137148
if (pendingAssignments) {
138149
pendingAssignments.push(value);
139150
value = inlineExpressions(pendingAssignments);
@@ -167,6 +178,10 @@ namespace ts {
167178
}
168179
return name;
169180
}
181+
182+
function emitRestAssignment(elements: BindingElement[], value: Expression, location: TextRange, original: Node) {
183+
emitAssignment(createObjectBindingPattern(elements), value, location, original);
184+
}
170185
}
171186

172187
/**
@@ -186,15 +201,17 @@ namespace ts {
186201

187202
const pendingAssignments: Expression[] = [];
188203

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

191206
const expression = inlineExpressions(pendingAssignments);
192207
aggregateTransformFlags(expression);
193208
return expression;
194209

195-
function emitAssignment(name: Identifier, value: Expression, location: TextRange, original: Node) {
210+
function emitAssignment(name: Identifier | ObjectLiteralExpression, value: Expression, location: TextRange, original: Node) {
196211
const expression = createAssignmentCallback
197-
? createAssignmentCallback(name, value, location)
212+
? createAssignmentCallback(name.kind === SyntaxKind.Identifier ? name : emitTempVariableAssignment(name, location),
213+
value,
214+
location)
198215
: createAssignment(name, value, location);
199216

200217
emitPendingAssignment(expression, original);
@@ -206,6 +223,10 @@ namespace ts {
206223
return name;
207224
}
208225

226+
function emitRestAssignment(elements: ObjectLiteralElementLike[], value: Expression, location: TextRange, original: Node) {
227+
emitAssignment(createObjectLiteral(elements), value, location, original);
228+
}
229+
209230
function emitPendingAssignment(expression: Expression, original: Node) {
210231
expression.original = original;
211232

@@ -223,6 +244,8 @@ namespace ts {
223244
location: TextRange,
224245
emitAssignment: (name: Identifier, value: Expression, location: TextRange, original: Node) => void,
225246
emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier,
247+
emitRestAssignment: (elements: (ObjectLiteralElementLike[] | BindingElement[]), value: Expression, location: TextRange, original: Node) => void,
248+
transformRest: boolean,
226249
visitor?: (node: Node) => VisitResult<Node>) {
227250
if (value && visitor) {
228251
value = visitNode(value, visitor, isExpression);
@@ -284,14 +307,39 @@ namespace ts {
284307
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment);
285308
}
286309

287-
for (const p of properties) {
310+
let es2015: ObjectLiteralElementLike[] = [];
311+
for (let i = 0; i < properties.length; i++) {
312+
const p = properties[i];
288313
if (p.kind === SyntaxKind.PropertyAssignment || p.kind === SyntaxKind.ShorthandPropertyAssignment) {
289-
const propName = <Identifier | LiteralExpression>(<PropertyAssignment>p).name;
290-
const target = p.kind === SyntaxKind.ShorthandPropertyAssignment ? <ShorthandPropertyAssignment>p : (<PropertyAssignment>p).initializer || propName;
291-
// Assignment for target = value.propName should highligh whole property, hence use p as source map node
292-
emitDestructuringAssignment(target, createDestructuringPropertyAccess(value, propName), p);
314+
if (!transformRest || p.transformFlags & TransformFlags.ContainsSpreadExpression) {
315+
if (es2015.length) {
316+
emitRestAssignment(es2015, value, location, target);
317+
es2015 = [];
318+
}
319+
const propName = <Identifier | LiteralExpression>(<PropertyAssignment>p).name;
320+
const bindingTarget = p.kind === SyntaxKind.ShorthandPropertyAssignment ? <ShorthandPropertyAssignment>p : (<PropertyAssignment>p).initializer || propName;
321+
// Assignment for bindingTarget = value.propName should highlight whole property, hence use p as source map node
322+
emitDestructuringAssignment(bindingTarget, createDestructuringPropertyAccess(value, propName), p);
323+
}
324+
else {
325+
es2015.push(p);
326+
}
327+
}
328+
else if (i === properties.length - 1 && p.kind === SyntaxKind.SpreadElementExpression) {
329+
Debug.assert((p as SpreadElementExpression).expression.kind === SyntaxKind.Identifier);
330+
if (es2015.length) {
331+
emitRestAssignment(es2015, value, location, target);
332+
es2015 = [];
333+
}
334+
const propName = (p as SpreadElementExpression).expression as Identifier;
335+
const restCall = createRestCall(value, target.properties, p => p.name, target);
336+
emitDestructuringAssignment(propName, restCall, p);
293337
}
294338
}
339+
if (es2015.length) {
340+
emitRestAssignment(es2015, value, location, target);
341+
es2015 = [];
342+
}
295343
}
296344

297345
function emitArrayLiteralAssignment(target: ArrayLiteralExpression, value: Expression, location: TextRange) {
@@ -318,10 +366,31 @@ namespace ts {
318366
}
319367
}
320368

369+
/** Given value: o, propName: p, pattern: { a, b, ...p } from the original statement
370+
* `{ a, b, ...p } = o`, create `p = __rest(o, ["a", "b"]);`*/
371+
function createRestCall<T extends Node>(value: Expression, elements: T[], getPropertyName: (element: T) => PropertyName, location: TextRange): Expression {
372+
const propertyNames: LiteralExpression[] = [];
373+
for (let i = 0; i < elements.length - 1; i++) {
374+
if (isOmittedExpression(elements[i])) {
375+
continue;
376+
}
377+
const str = <StringLiteral>createSynthesizedNode(SyntaxKind.StringLiteral);
378+
str.pos = location.pos;
379+
str.end = location.end;
380+
str.text = getTextOfPropertyName(getPropertyName(elements[i]));
381+
propertyNames.push(str);
382+
}
383+
const args = createSynthesizedNodeArray([value, createArrayLiteral(propertyNames, location)]);
384+
return createCall(createIdentifier("__rest"), undefined, args);
385+
}
386+
321387
function emitBindingElement(target: VariableDeclaration | ParameterDeclaration | BindingElement, value: Expression) {
322388
// Any temporary assignments needed to emit target = value should point to target
323389
const initializer = visitor ? visitNode(target.initializer, visitor, isExpression) : target.initializer;
324-
if (initializer) {
390+
if (transformRest) {
391+
value = value || initializer;
392+
}
393+
else if (initializer) {
325394
// Combine value and initializer
326395
value = value ? createDefaultValueCheck(value, initializer, target) : initializer;
327396
}
@@ -331,39 +400,83 @@ namespace ts {
331400
}
332401

333402
const name = target.name;
334-
if (isBindingPattern(name)) {
335-
const elements = name.elements;
336-
const numElements = elements.length;
403+
if (!isBindingPattern(name)) {
404+
emitAssignment(name, value, target, target);
405+
}
406+
else {
407+
const numElements = name.elements.length;
337408
if (numElements !== 1) {
338409
// For anything other than a single-element destructuring we need to generate a temporary
339410
// to ensure value is evaluated exactly once. Additionally, if we have zero elements
340411
// we need to emit *something* to ensure that in case a 'var' keyword was already emitted,
341412
// so in that case, we'll intentionally create that temporary.
342413
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ numElements !== 0, target, emitTempVariableAssignment);
343414
}
344-
for (let i = 0; i < numElements; i++) {
345-
const element = elements[i];
346-
if (isOmittedExpression(element)) {
347-
continue;
348-
}
349-
else if (name.kind === SyntaxKind.ObjectBindingPattern) {
350-
// Rewrite element to a declaration with an initializer that fetches property
351-
const propName = element.propertyName || <Identifier>element.name;
352-
emitBindingElement(element, createDestructuringPropertyAccess(value, propName));
415+
if (name.kind === SyntaxKind.ArrayBindingPattern) {
416+
emitArrayBindingElement(name, value);
417+
}
418+
else {
419+
emitObjectBindingElement(target, value);
420+
}
421+
}
422+
}
423+
424+
function emitArrayBindingElement(name: BindingPattern, value: Expression) {
425+
const elements = name.elements;
426+
const numElements = elements.length;
427+
for (let i = 0; i < numElements; i++) {
428+
const element = elements[i];
429+
if (isOmittedExpression(element)) {
430+
continue;
431+
}
432+
if (!element.dotDotDotToken) {
433+
// Rewrite element to a declaration that accesses array element at index i
434+
emitBindingElement(element, createElementAccess(value, i));
435+
}
436+
else if (i === numElements - 1) {
437+
emitBindingElement(element, createArraySlice(value, i));
438+
}
439+
}
440+
}
441+
442+
function emitObjectBindingElement(target: VariableDeclaration | ParameterDeclaration | BindingElement, value: Expression) {
443+
const name = target.name as BindingPattern;
444+
const elements = name.elements;
445+
const numElements = elements.length;
446+
let es2015: BindingElement[] = [];
447+
for (let i = 0; i < numElements; i++) {
448+
const element = elements[i];
449+
if (isOmittedExpression(element)) {
450+
continue;
451+
}
452+
if (i === numElements - 1 && element.dotDotDotToken) {
453+
if (es2015.length) {
454+
emitRestAssignment(es2015, value, target, target);
455+
es2015 = [];
353456
}
354-
else {
355-
if (!element.dotDotDotToken) {
356-
// Rewrite element to a declaration that accesses array element at index i
357-
emitBindingElement(element, createElementAccess(value, i));
358-
}
359-
else if (i === numElements - 1) {
360-
emitBindingElement(element, createArraySlice(value, i));
361-
}
457+
const restCall = createRestCall(value,
458+
name.elements,
459+
element => (element as BindingElement).propertyName || <Identifier>(element as BindingElement).name,
460+
name);
461+
emitBindingElement(element, restCall);
462+
}
463+
else if (!transformRest || element.transformFlags & TransformFlags.ContainsSpreadExpression) {
464+
if (es2015.length) {
465+
emitRestAssignment(es2015, value, target, target);
466+
es2015 = [];
362467
}
468+
// Rewrite element to a declaration with an initializer that fetches property
469+
const propName = element.propertyName || <Identifier>element.name;
470+
emitBindingElement(element, createDestructuringPropertyAccess(value, propName));
471+
}
472+
else {
473+
// do not emit until we have a complete bundle of ES2015 syntax
474+
es2015.push(element);
363475
}
364476
}
365-
else {
366-
emitAssignment(name, value, target, target);
477+
if (es2015.length) {
478+
emitRestAssignment(es2015, value, target, target);
479+
es2015 = [];
367480
}
368481
}
369482

0 commit comments

Comments
 (0)