Skip to content

Commit 945a836

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

File tree

3 files changed

+150
-3
lines changed

3 files changed

+150
-3
lines changed

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: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,50 @@ 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);
4246
}
47+
4348
return ts.visitEachChild(node, visitNode, context);
4449
}
4550

51+
function isMapForOfStatement(node: ts.Node): node is ts.ForOfStatement {
52+
if (!ts.isForOfStatement(node) || node.awaitModifier != null) {
53+
return false;
54+
}
55+
56+
const { expression } = node;
57+
58+
const expressionType = typeChecker.getTypeAtLocation(expression);
59+
60+
for (const subType of unionTypeParts(expressionType)) {
61+
assert(
62+
!(subType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)),
63+
'Can not use any or unknown values in for-of loop: ' +
64+
nodeLocationString(node),
65+
);
66+
67+
if (subType.flags & ts.TypeFlags.StringLike) {
68+
continue;
69+
}
70+
71+
const typeName = subType.getSymbol()?.getName();
72+
assert(typeName != null);
73+
74+
if (typeName === 'Map') {
75+
continue;
76+
}
77+
78+
return false;
79+
}
80+
81+
return true;
82+
}
83+
4684
function isArrayForOfStatement(node: ts.Node): node is ts.ForOfStatement {
4785
if (!ts.isForOfStatement(node) || node.awaitModifier != null) {
4886
return false;
@@ -91,6 +129,78 @@ export function optimizeForOf(program: ts.Program) {
91129
return (type.flags & ts.TypeFlags.Union) !== 0;
92130
}
93131

132+
function convertForOfStatementForMap(
133+
forOfNode: ts.ForOfStatement,
134+
): ts.Statement {
135+
const counter = factory.createLoopVariable();
136+
const forDeclarations = [
137+
factory.createVariableDeclaration(
138+
counter,
139+
undefined, // exclamationToken
140+
undefined, // type
141+
factory.createNumericLiteral(0),
142+
),
143+
];
144+
145+
// In the case where the user wrote an identifier as the RHS, like this:
146+
//
147+
// for (let v of arr) { }
148+
//
149+
// we don't want to emit a temporary variable for the RHS, just use it directly.
150+
const variableDeclarations = []
151+
let reference: ts.Identifier
152+
if (ts.isIdentifier(forOfNode.expression)) {
153+
reference = forOfNode.expression;
154+
} else {
155+
const temp = factory.createTempVariable(
156+
(identifier) => reference = identifier, // recordTempVariable
157+
);
158+
variableDeclarations.push(
159+
factory.createVariableDeclaration(
160+
temp,
161+
undefined, // exclamationToken
162+
undefined, // type
163+
forOfNode.expression,
164+
))
165+
}
166+
const valuesExpression = factory.createCallExpression(factory.createPropertyAccessExpression(reference!, factory.createIdentifier('values')), undefined, []);
167+
variableDeclarations.push(factory.createVariableDeclaration(`_${reference!.text}`, undefined, undefined, valuesExpression));
168+
const rhsReference = factory.createIdentifier(`_${reference!.text}`);
169+
170+
const forInitializer = factory.createVariableDeclarationList(
171+
forDeclarations,
172+
ts.NodeFlags.Let,
173+
);
174+
175+
const forCondition = factory.createLessThan(
176+
counter,
177+
factory.createPropertyAccessExpression(rhsReference, 'length'),
178+
);
179+
const forIncrementor = factory.createPostfixIncrement(counter);
180+
181+
assert(ts.isVariableDeclarationList(forOfNode.initializer));
182+
// It will use rhsIterationValue _a[_i] as the initializer.
183+
const itemAssignment = convertForOfInitializer(
184+
forOfNode.initializer,
185+
factory.createElementAccessExpression(rhsReference, counter),
186+
);
187+
188+
assert(ts.isBlock(forOfNode.statement));
189+
const forBody = factory.updateBlock(forOfNode.statement, [
190+
itemAssignment,
191+
...forOfNode.statement.statements,
192+
]);
193+
194+
const forStatement = factory.createForStatement(
195+
forInitializer,
196+
forCondition,
197+
forIncrementor,
198+
forBody,
199+
);
200+
201+
return factory.createBlock([factory.createVariableStatement(undefined, variableDeclarations), forStatement]);
202+
}
203+
94204
function convertForOfStatementForArray(
95205
forOfNode: ts.ForOfStatement,
96206
): ts.Statement {
@@ -126,7 +236,7 @@ export function optimizeForOf(program: ts.Program) {
126236
);
127237
}
128238

129-
const forInitiliazer = factory.createVariableDeclarationList(
239+
const forInitializer = factory.createVariableDeclarationList(
130240
forDeclarations,
131241
ts.NodeFlags.Let,
132242
);
@@ -151,7 +261,7 @@ export function optimizeForOf(program: ts.Program) {
151261
]);
152262

153263
return factory.createForStatement(
154-
forInitiliazer,
264+
forInitializer,
155265
forCondition,
156266
forIncrementor,
157267
forBody,

0 commit comments

Comments
 (0)