Skip to content

Commit 50817f8

Browse files
Support all for-of assignments and patterns (#831)
* Support all for-of initializers * Add unit tests * Merge reuse transformAssignmentPattern * Move destructure tests from loops to destructuring * Use testCases in destructuring * Update src/transformation/visitors/loops/for-of.ts Co-Authored-By: ark120202 <ark120202@gmail.com> * Add reference to TS bug in destructuring * Move tests above array destructuring optimization * Remove any cast from assignment dependency test Co-authored-by: ark120202 <ark120202@gmail.com>
1 parent 8733a3e commit 50817f8

File tree

5 files changed

+49
-77
lines changed

5 files changed

+49
-77
lines changed

src/transformation/utils/errors.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,6 @@ export const UnsupportedNonDestructuringLuaIterator = (node: ts.Node) =>
130130
export const UnresolvableRequirePath = (node: ts.Node, reason: string, path?: string) =>
131131
new TranspileError(`${reason}. TypeScript path: ${path}.`, node);
132132

133-
export const UnsupportedObjectDestructuringInForOf = (node: ts.Node) =>
134-
new TranspileError(`Unsupported object destructuring in for...of statement.`, node);
135-
136133
export const InvalidAmbientIdentifierName = (node: ts.Identifier) =>
137134
new TranspileError(
138135
`Invalid ambient identifier name "${node.text}". Ambient identifiers must be valid lua identifiers.`,

src/transformation/visitors/binary-expression/destructuring-assignments.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,20 @@ export function transformDestructuringAssignment(
3838
node: ts.DestructuringAssignment,
3939
root: lua.Expression
4040
): lua.Statement[] {
41-
switch (node.left.kind) {
42-
case ts.SyntaxKind.ObjectLiteralExpression:
43-
return transformObjectDestructuringAssignment(context, node as ts.ObjectDestructuringAssignment, root);
44-
case ts.SyntaxKind.ArrayLiteralExpression:
45-
return transformArrayDestructuringAssignment(context, node as ts.ArrayDestructuringAssignment, root);
46-
}
41+
return transformAssignmentPattern(context, node.left, root);
4742
}
4843

49-
function transformArrayDestructuringAssignment(
44+
export function transformAssignmentPattern(
5045
context: TransformationContext,
51-
node: ts.ArrayDestructuringAssignment,
46+
node: ts.AssignmentPattern,
5247
root: lua.Expression
5348
): lua.Statement[] {
54-
return transformArrayLiteralAssignmentPattern(context, node.left, root);
49+
switch (node.kind) {
50+
case ts.SyntaxKind.ObjectLiteralExpression:
51+
return transformObjectLiteralAssignmentPattern(context, node, root);
52+
case ts.SyntaxKind.ArrayLiteralExpression:
53+
return transformArrayLiteralAssignmentPattern(context, node, root);
54+
}
5555
}
5656

5757
function transformArrayLiteralAssignmentPattern(
@@ -132,14 +132,6 @@ function transformArrayLiteralAssignmentPattern(
132132
});
133133
}
134134

135-
function transformObjectDestructuringAssignment(
136-
context: TransformationContext,
137-
node: ts.ObjectDestructuringAssignment,
138-
root: lua.Expression
139-
): lua.Statement[] {
140-
return transformObjectLiteralAssignmentPattern(context, node.left, root);
141-
}
142-
143135
function transformObjectLiteralAssignmentPattern(
144136
context: TransformationContext,
145137
node: ts.ObjectLiteralExpression,

src/transformation/visitors/loops/for-of.ts

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
import * as ts from "typescript";
22
import * as lua from "../../../LuaAST";
3-
import { cast, castEach } from "../../../utils";
3+
import { castEach } from "../../../utils";
44
import { FunctionVisitor, TransformationContext } from "../../context";
55
import { AnnotationKind, getTypeAnnotations, isForRangeType, isLuaIteratorType } from "../../utils/annotations";
6-
import {
7-
InvalidForRangeCall,
8-
MissingForOfVariables,
9-
UnsupportedNonDestructuringLuaIterator,
10-
UnsupportedObjectDestructuringInForOf,
11-
} from "../../utils/errors";
12-
import { createUnpackCall } from "../../utils/lua-ast";
6+
import { InvalidForRangeCall, MissingForOfVariables, UnsupportedNonDestructuringLuaIterator } from "../../utils/errors";
137
import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib";
14-
import { isArrayType, isNumberType } from "../../utils/typescript";
8+
import { isArrayType, isNumberType, isAssignmentPattern } from "../../utils/typescript";
159
import { transformArguments } from "../call";
1610
import { transformIdentifier } from "../identifier";
1711
import {
@@ -20,6 +14,8 @@ import {
2014
transformVariableDeclaration,
2115
} from "../variable-declaration";
2216
import { getVariableDeclarationBinding, transformLoopBody } from "./utils";
17+
import { transformAssignment } from "../binary-expression/assignments";
18+
import { transformAssignmentPattern } from "../binary-expression/destructuring-assignments";
2319

2420
function transformForOfInitializer(
2521
context: TransformationContext,
@@ -29,15 +25,8 @@ function transformForOfInitializer(
2925
if (ts.isVariableDeclarationList(initializer)) {
3026
const binding = getVariableDeclarationBinding(initializer);
3127
// Declaration of new variable
32-
if (ts.isArrayBindingPattern(binding)) {
33-
if (binding.elements.length === 0) {
34-
// Ignore empty destructuring assignment
35-
return [];
36-
}
37-
28+
if (ts.isArrayBindingPattern(binding) || ts.isObjectBindingPattern(binding)) {
3829
return transformBindingPattern(context, binding, expression);
39-
} else if (ts.isObjectBindingPattern(binding)) {
40-
throw UnsupportedObjectDestructuringInForOf(initializer);
4130
}
4231

4332
const variableStatements = transformVariableDeclaration(context, initializer.declarations[0]);
@@ -53,27 +42,13 @@ function transformForOfInitializer(
5342
throw MissingForOfVariables(initializer);
5443
}
5544
} else {
56-
// Assignment to existing variable
57-
let variables: lua.AssignmentLeftHandSideExpression | lua.AssignmentLeftHandSideExpression[];
58-
let valueExpression: lua.Expression = expression;
59-
if (ts.isArrayLiteralExpression(initializer)) {
60-
if (initializer.elements.length > 0) {
61-
valueExpression = createUnpackCall(context, expression, initializer);
62-
variables = castEach(
63-
initializer.elements.map(e => context.transformExpression(e)),
64-
lua.isAssignmentLeftHandSideExpression
65-
);
66-
} else {
67-
// Ignore empty destructring assignment
68-
return [];
69-
}
70-
} else if (ts.isObjectLiteralExpression(initializer)) {
71-
throw UnsupportedObjectDestructuringInForOf(initializer);
72-
} else {
73-
variables = cast(context.transformExpression(initializer), lua.isAssignmentLeftHandSideExpression);
45+
// Assignment to existing variable(s)
46+
47+
if (isAssignmentPattern(initializer)) {
48+
return transformAssignmentPattern(context, initializer, expression);
7449
}
7550

76-
return [lua.createAssignmentStatement(variables, valueExpression)];
51+
return transformAssignment(context, initializer, expression);
7752
}
7853
}
7954

test/unit/destructuring.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,34 @@ test.each(["[]", "{}"])("empty binding pattern", bindingPattern => {
9696
`.expectToMatchJsResult();
9797
});
9898

99+
// TODO: https://github.com/microsoft/TypeScript/pull/35906
100+
// Adjust this test to use expectToMatchJsResult() and testCases when this issue is fixed.
101+
test.each([
102+
["foo", "['bar']"],
103+
["[foo]", "[['bar']]"],
104+
["[foo = 'bar']", "[[]]"],
105+
["{ foo }", "[{ foo: 'bar' }]"],
106+
["{ x: foo }", "[{ x: 'bar' }]"],
107+
["{ foo = 'bar' }", "[{}] as { foo?: string }[]"],
108+
])("forof assignment updates dependencies", (initializer, expression) => {
109+
util.testModule`
110+
let foo = '';
111+
export { foo };
112+
for (${initializer} of ${expression}) {}
113+
`
114+
.setReturnExport("foo")
115+
.expectToEqual("bar");
116+
});
117+
118+
test.each(testCases)("forof variable declaration binding patterns (%p)", ({ binding, value }) => {
119+
util.testFunction`
120+
let ${allBindings};
121+
for (const ${binding} of [${value} as any]) {
122+
return { ${allBindings} };
123+
}
124+
`.expectToMatchJsResult();
125+
});
126+
99127
describe("array destructuring optimization", () => {
100128
// TODO: Try to generalize optimization logic between declaration and assignment and make more generic tests
101129

test/unit/loops.spec.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import * as ts from "typescript";
22
import * as tstl from "../../src";
3-
import {
4-
ForbiddenForIn,
5-
UnsupportedForTarget,
6-
UnsupportedObjectDestructuringInForOf,
7-
} from "../../src/transformation/utils/errors";
3+
import { ForbiddenForIn, UnsupportedForTarget } from "../../src/transformation/utils/errors";
84
import * as util from "../util";
95

106
test("while", () => {
@@ -428,22 +424,6 @@ test("forof array which modifies length", () => {
428424
`.expectToMatchJsResult();
429425
});
430426

431-
test.each([
432-
{ initializer: "const {a, b}", vars: "" },
433-
{ initializer: "const {a: x, b: y}", vars: "" },
434-
{ initializer: "{a, b}", vars: "let a: string, b: string;" },
435-
{ initializer: "{a: c, b: d}", vars: "let c: string, d: string;" },
436-
])("forof object destructuring (%p)", ({ initializer, vars }) => {
437-
const code = `
438-
declare const arr: {a: string, b: string}[];
439-
${vars}
440-
for (${initializer} of arr) {}`;
441-
442-
expect(() => util.transpileString(code)).toThrow(
443-
UnsupportedObjectDestructuringInForOf(ts.createEmptyStatement()).message
444-
);
445-
});
446-
447427
test("forof nested destructuring", () => {
448428
util.testFunction`
449429
let result = 0;

0 commit comments

Comments
 (0)