Skip to content

Commit fb887db

Browse files
committed
Try out array destructuring
1 parent 2cffe00 commit fb887db

File tree

4 files changed

+297
-20
lines changed

4 files changed

+297
-20
lines changed

benchmark/introspectionFromSchema-benchmark.js

Lines changed: 0 additions & 17 deletions
This file was deleted.

resources/build-npm.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ts from 'typescript';
66

77
import { changeExtensionInImportPaths } from './change-extension-in-import-paths.js';
88
import { inlineInvariant } from './inline-invariant.js';
9+
import { optimizeArrayDestructuring } from './optimize-array-destructuring.js';
910
import { optimizeForOf } from './optimize-for-of.js';
1011
import {
1112
prettify,
@@ -146,7 +147,7 @@ function emitTSFiles(options: {
146147

147148
const tsProgram = ts.createProgram(['src/index.ts'], tsOptions, tsHost);
148149
const tsResult = tsProgram.emit(undefined, undefined, undefined, undefined, {
149-
before: [optimizeForOf(tsProgram)],
150+
before: [optimizeForOf(tsProgram), optimizeArrayDestructuring()],
150151
after: [changeExtensionInImportPaths({ extension }), inlineInvariant],
151152
});
152153
assert(
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import ts from 'typescript';
2+
3+
export function optimizeArrayDestructuring() {
4+
const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
5+
const { factory } = context;
6+
7+
return (sourceFile: ts.SourceFile): ts.SourceFile => {
8+
const visitor = (node: ts.Node): ts.Node => {
9+
if (ts.isArrayBindingPattern(node)) {
10+
const elements = node.elements
11+
.map((el, i) => {
12+
if (!el.getText()) {
13+
return undefined;
14+
}
15+
return { key: String(i), name: el.getText() };
16+
})
17+
.filter(Boolean) as Array<{ key: string; name: string }>;
18+
19+
const objectBindingPattern = factory.createObjectBindingPattern(
20+
elements.map((el) => {
21+
const key = factory.createIdentifier(el.key);
22+
const name = factory.createIdentifier(el.name);
23+
return factory.createBindingElement(undefined, key, name);
24+
}),
25+
);
26+
return objectBindingPattern;
27+
}
28+
29+
return ts.visitEachChild(node, visitor, context);
30+
};
31+
return ts.visitNode(sourceFile, visitor) as ts.SourceFile;
32+
};
33+
};
34+
35+
return transformer;
36+
}

resources/optimize-for-of.ts

Lines changed: 259 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,85 @@ export function optimizeForOf(program: ts.Program) {
3737
return ts.visitNode(sourceFile, visitNode) as ts.SourceFile;
3838

3939
function visitNode(node: ts.Node): ts.Node {
40+
// TODO: add Set support
41+
// TODO: potentially add support for for..in statements as well
4042
if (isArrayForOfStatement(node)) {
4143
return convertForOfStatementForArray(node);
44+
} else if (isMapForOfStatement(node)) {
45+
return convertForOfStatementForMap(node);
46+
} else if (isSetForOfStatement(node)) {
47+
return convertForOfStatementForSet(node);
4248
}
49+
4350
return ts.visitEachChild(node, visitNode, context);
4451
}
4552

53+
function isSetForOfStatement(node: ts.Node): node is ts.ForOfStatement {
54+
if (!ts.isForOfStatement(node) || node.awaitModifier != null) {
55+
return false;
56+
}
57+
58+
const { expression } = node;
59+
60+
const expressionType = typeChecker.getTypeAtLocation(expression);
61+
62+
for (const subType of unionTypeParts(expressionType)) {
63+
assert(
64+
!(subType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)),
65+
'Can not use any or unknown values in for-of loop: ' +
66+
nodeLocationString(node),
67+
);
68+
69+
if (subType.flags & ts.TypeFlags.StringLike) {
70+
continue;
71+
}
72+
73+
const typeName = subType.getSymbol()?.getName();
74+
assert(typeName != null);
75+
76+
if (typeName === 'Set') {
77+
continue;
78+
}
79+
80+
return false;
81+
}
82+
83+
return true;
84+
}
85+
86+
function isMapForOfStatement(node: ts.Node): node is ts.ForOfStatement {
87+
if (!ts.isForOfStatement(node) || node.awaitModifier != null) {
88+
return false;
89+
}
90+
91+
const { expression } = node;
92+
93+
const expressionType = typeChecker.getTypeAtLocation(expression);
94+
95+
for (const subType of unionTypeParts(expressionType)) {
96+
assert(
97+
!(subType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)),
98+
'Can not use any or unknown values in for-of loop: ' +
99+
nodeLocationString(node),
100+
);
101+
102+
if (subType.flags & ts.TypeFlags.StringLike) {
103+
continue;
104+
}
105+
106+
const typeName = subType.getSymbol()?.getName();
107+
assert(typeName != null);
108+
109+
if (typeName === 'Map') {
110+
continue;
111+
}
112+
113+
return false;
114+
}
115+
116+
return true;
117+
}
118+
46119
function isArrayForOfStatement(node: ts.Node): node is ts.ForOfStatement {
47120
if (!ts.isForOfStatement(node) || node.awaitModifier != null) {
48121
return false;
@@ -91,6 +164,190 @@ export function optimizeForOf(program: ts.Program) {
91164
return (type.flags & ts.TypeFlags.Union) !== 0;
92165
}
93166

167+
function convertForOfStatementForSet(
168+
forOfNode: ts.ForOfStatement,
169+
): ts.Statement {
170+
const counter = factory.createLoopVariable();
171+
const forDeclarations = [
172+
factory.createVariableDeclaration(
173+
counter,
174+
undefined, // exclamationToken
175+
undefined, // type
176+
factory.createNumericLiteral(0),
177+
),
178+
];
179+
180+
// In the case where the user wrote an identifier as the RHS, like this:
181+
//
182+
// for (let v of arr) { }
183+
//
184+
// we don't want to emit a temporary variable for the RHS, just use it directly.
185+
const variableDeclarations = [];
186+
let reference: ts.Identifier = factory.createIdentifier('temp');
187+
if (ts.isIdentifier(forOfNode.expression)) {
188+
reference = forOfNode.expression;
189+
} else {
190+
const temp = factory.createTempVariable(
191+
(identifier) => (reference = identifier), // recordTempVariable
192+
);
193+
variableDeclarations.push(
194+
factory.createVariableDeclaration(
195+
temp,
196+
undefined, // exclamationToken
197+
undefined, // type
198+
forOfNode.expression,
199+
),
200+
);
201+
}
202+
203+
const tempText = `_${reference.text}`;
204+
const valuesExpression = factory.createCallExpression(
205+
factory.createPropertyAccessExpression(
206+
reference,
207+
factory.createIdentifier('values'),
208+
),
209+
undefined,
210+
[],
211+
);
212+
variableDeclarations.push(
213+
factory.createVariableDeclaration(
214+
tempText,
215+
undefined,
216+
undefined,
217+
valuesExpression,
218+
),
219+
);
220+
const rhsReference = factory.createIdentifier(tempText);
221+
222+
const forInitializer = factory.createVariableDeclarationList(
223+
forDeclarations,
224+
ts.NodeFlags.Let,
225+
);
226+
227+
const forCondition = factory.createLessThan(
228+
counter,
229+
factory.createPropertyAccessExpression(rhsReference, 'length'),
230+
);
231+
const forIncrementor = factory.createPostfixIncrement(counter);
232+
233+
assert(ts.isVariableDeclarationList(forOfNode.initializer));
234+
// It will use rhsIterationValue _a[_i] as the initializer.
235+
const itemAssignment = convertForOfInitializer(
236+
forOfNode.initializer,
237+
factory.createElementAccessExpression(rhsReference, counter),
238+
);
239+
240+
assert(ts.isBlock(forOfNode.statement));
241+
const forBody = factory.updateBlock(forOfNode.statement, [
242+
itemAssignment,
243+
...forOfNode.statement.statements,
244+
]);
245+
246+
const forStatement = factory.createForStatement(
247+
forInitializer,
248+
forCondition,
249+
forIncrementor,
250+
forBody,
251+
);
252+
253+
return factory.createBlock([
254+
factory.createVariableStatement(undefined, variableDeclarations),
255+
forStatement,
256+
]);
257+
}
258+
259+
function convertForOfStatementForMap(
260+
forOfNode: ts.ForOfStatement,
261+
): ts.Statement {
262+
const counter = factory.createLoopVariable();
263+
const forDeclarations = [
264+
factory.createVariableDeclaration(
265+
counter,
266+
undefined, // exclamationToken
267+
undefined, // type
268+
factory.createNumericLiteral(0),
269+
),
270+
];
271+
272+
// In the case where the user wrote an identifier as the RHS, like this:
273+
//
274+
// for (let v of arr) { }
275+
//
276+
// we don't want to emit a temporary variable for the RHS, just use it directly.
277+
const variableDeclarations = [];
278+
let reference: ts.Identifier = factory.createIdentifier('temp');
279+
if (ts.isIdentifier(forOfNode.expression)) {
280+
reference = forOfNode.expression;
281+
} else {
282+
const temp = factory.createTempVariable(
283+
(identifier) => (reference = identifier), // recordTempVariable
284+
);
285+
variableDeclarations.push(
286+
factory.createVariableDeclaration(
287+
temp,
288+
undefined, // exclamationToken
289+
undefined, // type
290+
forOfNode.expression,
291+
),
292+
);
293+
}
294+
295+
const tempText = `_${reference.text}`;
296+
const valuesExpression = factory.createCallExpression(
297+
factory.createPropertyAccessExpression(
298+
reference,
299+
factory.createIdentifier('values'),
300+
),
301+
undefined,
302+
[],
303+
);
304+
variableDeclarations.push(
305+
factory.createVariableDeclaration(
306+
tempText,
307+
undefined,
308+
undefined,
309+
valuesExpression,
310+
),
311+
);
312+
const rhsReference = factory.createIdentifier(tempText);
313+
314+
const forInitializer = factory.createVariableDeclarationList(
315+
forDeclarations,
316+
ts.NodeFlags.Let,
317+
);
318+
319+
const forCondition = factory.createLessThan(
320+
counter,
321+
factory.createPropertyAccessExpression(rhsReference, 'length'),
322+
);
323+
const forIncrementor = factory.createPostfixIncrement(counter);
324+
325+
assert(ts.isVariableDeclarationList(forOfNode.initializer));
326+
// It will use rhsIterationValue _a[_i] as the initializer.
327+
const itemAssignment = convertForOfInitializer(
328+
forOfNode.initializer,
329+
factory.createElementAccessExpression(rhsReference, counter),
330+
);
331+
332+
assert(ts.isBlock(forOfNode.statement));
333+
const forBody = factory.updateBlock(forOfNode.statement, [
334+
itemAssignment,
335+
...forOfNode.statement.statements,
336+
]);
337+
338+
const forStatement = factory.createForStatement(
339+
forInitializer,
340+
forCondition,
341+
forIncrementor,
342+
forBody,
343+
);
344+
345+
return factory.createBlock([
346+
factory.createVariableStatement(undefined, variableDeclarations),
347+
forStatement,
348+
]);
349+
}
350+
94351
function convertForOfStatementForArray(
95352
forOfNode: ts.ForOfStatement,
96353
): ts.Statement {
@@ -126,7 +383,7 @@ export function optimizeForOf(program: ts.Program) {
126383
);
127384
}
128385

129-
const forInitiliazer = factory.createVariableDeclarationList(
386+
const forInitializer = factory.createVariableDeclarationList(
130387
forDeclarations,
131388
ts.NodeFlags.Let,
132389
);
@@ -151,7 +408,7 @@ export function optimizeForOf(program: ts.Program) {
151408
]);
152409

153410
return factory.createForStatement(
154-
forInitiliazer,
411+
forInitializer,
155412
forCondition,
156413
forIncrementor,
157414
forBody,

0 commit comments

Comments
 (0)