Skip to content

Commit 8241018

Browse files
authored
Bugfix/loop destructuring (#783)
* Use general destructuring logic for loop variable destructuring * Fixed destructuring variables from function headers not being local * Reworked transformation of for initializers a little to pass tests * PR feedback * Refactored destruction logic slightly * Upgraded some tests * changed parameter type * renamed methods, updated tests * inlined test input * Fixed incorrect handling of empty binding patterns * Fixed problem in new test * Removed superfluous pattern length check
1 parent 295d907 commit 8241018

File tree

8 files changed

+343
-368
lines changed

8 files changed

+343
-368
lines changed

src/transformation/visitors/class/members/constructor.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { transformParameters, transformFunctionBodyStatements, transformFunction
55
import { TransformationContext } from "../../../context";
66
import { transformIdentifier } from "../../identifier";
77
import { transformClassInstanceFields } from "./fields";
8+
import { pushScope, ScopeType, popScope } from "../../../utils/scope";
89

910
export function createConstructorName(className: lua.Identifier): lua.TableIndexExpression {
1011
return lua.createTableIndexExpression(
@@ -26,7 +27,8 @@ export function transformConstructorDeclaration(
2627
}
2728

2829
// Transform body
29-
const [body, scope] = transformFunctionBodyStatements(context, statement.body);
30+
const scope = pushScope(context, ScopeType.Function);
31+
const body = transformFunctionBodyStatements(context, statement.body);
3032

3133
const [params, dotsLiteral, restParamName] = transformParameters(
3234
context,
@@ -81,6 +83,8 @@ export function transformConstructorDeclaration(
8183

8284
const constructorWasGenerated = statement.pos === -1;
8385

86+
popScope(context);
87+
8488
return lua.createAssignmentStatement(
8589
createConstructorName(className),
8690
lua.createFunctionExpression(

src/transformation/visitors/function.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,9 @@ function isRestParameterReferenced(context: TransformationContext, identifier: l
5353
return references.some(r => !r.parent || !ts.isSpreadElement(r.parent) || !isVarArgType(context, r));
5454
}
5555

56-
export function transformFunctionBodyStatements(
57-
context: TransformationContext,
58-
body: ts.Block
59-
): [lua.Statement[], Scope] {
60-
pushScope(context, ScopeType.Function);
56+
export function transformFunctionBodyStatements(context: TransformationContext, body: ts.Block): lua.Statement[] {
6157
const bodyStatements = performHoisting(context, context.transformStatements(body.statements));
62-
const scope = popScope(context);
63-
return [bodyStatements, scope];
58+
return bodyStatements;
6459
}
6560

6661
export function transformFunctionBodyHeader(
@@ -116,8 +111,10 @@ export function transformFunctionBody(
116111
body: ts.Block,
117112
spreadIdentifier?: lua.Identifier
118113
): [lua.Statement[], Scope] {
119-
const [bodyStatements, scope] = transformFunctionBodyStatements(context, body);
114+
const scope = pushScope(context, ScopeType.Function);
115+
const bodyStatements = transformFunctionBodyStatements(context, body);
120116
const headerStatements = transformFunctionBodyHeader(context, scope, parameters, spreadIdentifier);
117+
popScope(context);
121118
return [[...headerStatements, ...bodyStatements], scope];
122119
}
123120

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

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,59 +14,66 @@ import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib";
1414
import { isArrayType, isNumberType } from "../../utils/typescript";
1515
import { transformArguments } from "../call";
1616
import { transformIdentifier } from "../identifier";
17-
import { transformArrayBindingElement, transformVariableDeclaration } from "../variable-declaration";
17+
import {
18+
transformBindingPattern,
19+
transformArrayBindingElement,
20+
transformVariableDeclaration,
21+
} from "../variable-declaration";
1822
import { getVariableDeclarationBinding, transformLoopBody } from "./utils";
1923

2024
function transformForOfInitializer(
2125
context: TransformationContext,
2226
initializer: ts.ForInitializer,
23-
expression: lua.Expression
24-
): lua.Statement | undefined {
27+
expression: lua.Identifier
28+
): lua.Statement[] {
2529
if (ts.isVariableDeclarationList(initializer)) {
2630
const binding = getVariableDeclarationBinding(initializer);
2731
// Declaration of new variable
2832
if (ts.isArrayBindingPattern(binding)) {
2933
if (binding.elements.length === 0) {
3034
// Ignore empty destructuring assignment
31-
return undefined;
35+
return [];
3236
}
3337

34-
expression = createUnpackCall(context, expression, initializer);
38+
return transformBindingPattern(context, binding, expression);
3539
} else if (ts.isObjectBindingPattern(binding)) {
3640
throw UnsupportedObjectDestructuringInForOf(initializer);
3741
}
3842

3943
const variableStatements = transformVariableDeclaration(context, initializer.declarations[0]);
4044
if (variableStatements[0]) {
4145
// we can safely assume that for vars are not exported and therefore declarationstatenents
42-
return lua.createVariableDeclarationStatement(
43-
(variableStatements[0] as lua.VariableDeclarationStatement).left,
44-
expression
45-
);
46+
return [
47+
lua.createVariableDeclarationStatement(
48+
(variableStatements[0] as lua.VariableDeclarationStatement).left,
49+
expression
50+
),
51+
];
4652
} else {
4753
throw MissingForOfVariables(initializer);
4854
}
4955
} else {
5056
// Assignment to existing variable
5157
let variables: lua.AssignmentLeftHandSideExpression | lua.AssignmentLeftHandSideExpression[];
58+
let valueExpression: lua.Expression = expression;
5259
if (ts.isArrayLiteralExpression(initializer)) {
5360
if (initializer.elements.length > 0) {
54-
expression = createUnpackCall(context, expression, initializer);
61+
valueExpression = createUnpackCall(context, expression, initializer);
5562
variables = castEach(
5663
initializer.elements.map(e => context.transformExpression(e)),
5764
lua.isAssignmentLeftHandSideExpression
5865
);
5966
} else {
6067
// Ignore empty destructring assignment
61-
return undefined;
68+
return [];
6269
}
6370
} else if (ts.isObjectLiteralExpression(initializer)) {
6471
throw UnsupportedObjectDestructuringInForOf(initializer);
6572
} else {
6673
variables = cast(context.transformExpression(initializer), lua.isAssignmentLeftHandSideExpression);
6774
}
6875

69-
return lua.createAssignmentStatement(variables, expression);
76+
return [lua.createAssignmentStatement(variables, valueExpression)];
7077
}
7178
}
7279

@@ -178,7 +185,7 @@ function transformForOfLuaIteratorStatement(
178185
const valueVariable = lua.createIdentifier("____value");
179186
const initializer = transformForOfInitializer(context, statement.initializer, valueVariable);
180187
if (initializer) {
181-
block.statements.splice(0, 0, initializer);
188+
block.statements.splice(0, 0, ...initializer);
182189
}
183190
return lua.createForInStatement(block, [valueVariable], [luaIterator]);
184191
}
@@ -198,7 +205,7 @@ function transformForOfArrayStatement(
198205
valueVariable = lua.createIdentifier("____values");
199206
const initializer = transformForOfInitializer(context, statement.initializer, valueVariable);
200207
if (initializer) {
201-
block.statements.unshift(initializer);
208+
block.statements.unshift(...initializer);
202209
}
203210
} else {
204211
valueVariable = transformIdentifier(context, binding);
@@ -208,7 +215,7 @@ function transformForOfArrayStatement(
208215
valueVariable = lua.createIdentifier("____value");
209216
const initializer = transformForOfInitializer(context, statement.initializer, valueVariable);
210217
if (initializer) {
211-
block.statements.unshift(initializer);
218+
block.statements.unshift(...initializer);
212219
}
213220
}
214221

@@ -244,7 +251,7 @@ function transformForOfIteratorStatement(
244251
const valueVariable = lua.createIdentifier("____value");
245252
const initializer = transformForOfInitializer(context, statement.initializer, valueVariable);
246253
if (initializer) {
247-
block.statements.unshift(initializer);
254+
block.statements.unshift(...initializer);
248255
}
249256

250257
return lua.createForInStatement(

src/transformation/visitors/switch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const transformSwitchStatement: FunctionVisitor<ts.SwitchStatement> = (st
1111
}
1212

1313
const scope = pushScope(context, ScopeType.Switch);
14+
1415
// Give the switch a unique name to prevent nested switches from acting up.
1516
const switchName = `____switch${scope.id}`;
1617
const switchVariable = lua.createIdentifier(switchName);

src/transformation/visitors/variable-declaration.ts

Lines changed: 87 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,92 @@ export function transformBindingPattern(
120120
return result;
121121
}
122122

123+
export function transformBindingVariableDeclaration(
124+
context: TransformationContext,
125+
bindingPattern: ts.BindingPattern,
126+
initializer?: ts.Expression
127+
): lua.Statement[] {
128+
const statements: lua.Statement[] = [];
129+
130+
// For object, nested or rest bindings fall back to transformBindingPattern
131+
const isComplexBindingElement = (e: ts.ArrayBindingElement) =>
132+
ts.isBindingElement(e) && (!ts.isIdentifier(e.name) || e.dotDotDotToken);
133+
134+
if (ts.isObjectBindingPattern(bindingPattern) || bindingPattern.elements.some(isComplexBindingElement)) {
135+
let table: lua.Identifier;
136+
if (initializer !== undefined && ts.isIdentifier(initializer)) {
137+
table = transformIdentifier(context, initializer);
138+
} else {
139+
// Contain the expression in a temporary variable
140+
table = lua.createAnonymousIdentifier();
141+
if (initializer) {
142+
statements.push(
143+
lua.createVariableDeclarationStatement(table, context.transformExpression(initializer))
144+
);
145+
}
146+
}
147+
statements.push(...transformBindingPattern(context, bindingPattern, table));
148+
return statements;
149+
}
150+
151+
const vars =
152+
bindingPattern.elements.length > 0
153+
? bindingPattern.elements.map(e => transformArrayBindingElement(context, e))
154+
: lua.createAnonymousIdentifier();
155+
156+
if (initializer) {
157+
if (isTupleReturnCall(context, initializer)) {
158+
// Don't unpack @tupleReturn annotated functions
159+
statements.push(
160+
...createLocalOrExportedOrGlobalDeclaration(
161+
context,
162+
vars,
163+
context.transformExpression(initializer),
164+
initializer
165+
)
166+
);
167+
} else if (ts.isArrayLiteralExpression(initializer)) {
168+
// Don't unpack array literals
169+
const values =
170+
initializer.elements.length > 0
171+
? initializer.elements.map(e => context.transformExpression(e))
172+
: lua.createNilLiteral();
173+
statements.push(...createLocalOrExportedOrGlobalDeclaration(context, vars, values, initializer));
174+
} else {
175+
// local vars = this.transpileDestructingAssignmentValue(node.initializer);
176+
const unpackedInitializer = createUnpackCall(
177+
context,
178+
context.transformExpression(initializer),
179+
initializer
180+
);
181+
statements.push(
182+
...createLocalOrExportedOrGlobalDeclaration(context, vars, unpackedInitializer, initializer)
183+
);
184+
}
185+
} else {
186+
statements.push(
187+
...createLocalOrExportedOrGlobalDeclaration(context, vars, lua.createNilLiteral(), initializer)
188+
);
189+
}
190+
191+
for (const element of bindingPattern.elements) {
192+
if (!ts.isOmittedExpression(element) && element.initializer) {
193+
const variableName = transformIdentifier(context, element.name as ts.Identifier);
194+
const identifier = addExportToIdentifier(context, variableName);
195+
statements.push(
196+
lua.createIfStatement(
197+
lua.createBinaryExpression(identifier, lua.createNilLiteral(), lua.SyntaxKind.EqualityOperator),
198+
lua.createBlock([
199+
lua.createAssignmentStatement(identifier, context.transformExpression(element.initializer)),
200+
])
201+
)
202+
);
203+
}
204+
}
205+
206+
return statements;
207+
}
208+
123209
// TODO: FunctionVisitor<ts.VariableDeclaration>
124210
export function transformVariableDeclaration(
125211
context: TransformationContext,
@@ -137,86 +223,7 @@ export function transformVariableDeclaration(
137223
const value = statement.initializer && context.transformExpression(statement.initializer);
138224
return createLocalOrExportedOrGlobalDeclaration(context, identifierName, value, statement);
139225
} else if (ts.isArrayBindingPattern(statement.name) || ts.isObjectBindingPattern(statement.name)) {
140-
const statements: lua.Statement[] = [];
141-
142-
// For object, nested or rest bindings fall back to transformBindingPattern
143-
if (
144-
ts.isObjectBindingPattern(statement.name) ||
145-
statement.name.elements.some(e => ts.isBindingElement(e) && (!ts.isIdentifier(e.name) || e.dotDotDotToken))
146-
) {
147-
let table: lua.Identifier;
148-
if (statement.initializer !== undefined && ts.isIdentifier(statement.initializer)) {
149-
table = transformIdentifier(context, statement.initializer);
150-
} else {
151-
// Contain the expression in a temporary variable
152-
table = lua.createAnonymousIdentifier();
153-
if (statement.initializer) {
154-
statements.push(
155-
lua.createVariableDeclarationStatement(
156-
table,
157-
context.transformExpression(statement.initializer)
158-
)
159-
);
160-
}
161-
}
162-
statements.push(...transformBindingPattern(context, statement.name, table));
163-
return statements;
164-
}
165-
166-
const vars =
167-
statement.name.elements.length > 0
168-
? statement.name.elements.map(e => transformArrayBindingElement(context, e))
169-
: lua.createAnonymousIdentifier(statement.name);
170-
171-
if (statement.initializer) {
172-
if (isTupleReturnCall(context, statement.initializer)) {
173-
// Don't unpack @tupleReturn annotated functions
174-
statements.push(
175-
...createLocalOrExportedOrGlobalDeclaration(
176-
context,
177-
vars,
178-
context.transformExpression(statement.initializer),
179-
statement
180-
)
181-
);
182-
} else if (ts.isArrayLiteralExpression(statement.initializer)) {
183-
// Don't unpack array literals
184-
const values =
185-
statement.initializer.elements.length > 0
186-
? statement.initializer.elements.map(e => context.transformExpression(e))
187-
: lua.createNilLiteral();
188-
statements.push(...createLocalOrExportedOrGlobalDeclaration(context, vars, values, statement));
189-
} else {
190-
// local vars = this.transpileDestructingAssignmentValue(node.initializer);
191-
const initializer = createUnpackCall(
192-
context,
193-
context.transformExpression(statement.initializer),
194-
statement.initializer
195-
);
196-
statements.push(...createLocalOrExportedOrGlobalDeclaration(context, vars, initializer, statement));
197-
}
198-
} else {
199-
statements.push(
200-
...createLocalOrExportedOrGlobalDeclaration(context, vars, lua.createNilLiteral(), statement)
201-
);
202-
}
203-
204-
for (const element of statement.name.elements) {
205-
if (!ts.isOmittedExpression(element) && element.initializer) {
206-
const variableName = transformIdentifier(context, element.name as ts.Identifier);
207-
const identifier = addExportToIdentifier(context, variableName);
208-
statements.push(
209-
lua.createIfStatement(
210-
lua.createBinaryExpression(identifier, lua.createNilLiteral(), lua.SyntaxKind.EqualityOperator),
211-
lua.createBlock([
212-
lua.createAssignmentStatement(identifier, context.transformExpression(element.initializer)),
213-
])
214-
)
215-
);
216-
}
217-
}
218-
219-
return statements;
226+
return transformBindingVariableDeclaration(context, statement.name, statement.initializer);
220227
} else {
221228
return assertNever(statement.name);
222229
}

0 commit comments

Comments
 (0)