Skip to content

Commit 1f3a505

Browse files
authored
fixes to binding destructuriing in loops and functions (#1182)
Co-authored-by: Tom <tomblind@users.noreply.github.com>
1 parent ed3e785 commit 1f3a505

File tree

5 files changed

+47
-2
lines changed

5 files changed

+47
-2
lines changed

src/transformation/utils/scope.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export enum ScopeType {
1414
Block = 1 << 5,
1515
Try = 1 << 6,
1616
Catch = 1 << 7,
17+
LoopInitializer = 1 << 8,
1718
}
1819

1920
interface FunctionDefinitionInfo {

src/transformation/visitors/function.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@ export function transformFunctionBodyHeader(
8585
}
8686

8787
// Binding pattern
88-
bindingPatternDeclarations.push(...transformBindingPattern(context, declaration.name, identifier));
88+
const name = declaration.name;
89+
const [precedingStatements, bindings] = transformInPrecedingStatementScope(context, () =>
90+
transformBindingPattern(context, name, identifier)
91+
);
92+
bindingPatternDeclarations.push(...precedingStatements, ...bindings);
8993
} else if (declaration.initializer !== undefined) {
9094
// Default parameter
9195
headerStatements.push(

src/transformation/visitors/loops/utils.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as ts from "typescript";
22
import * as lua from "../../../LuaAST";
33
import { TransformationContext } from "../../context";
4+
import { transformInPrecedingStatementScope } from "../../utils/preceding-statements";
45
import { performHoisting, popScope, pushScope, ScopeType } from "../../utils/scope";
56
import { isAssignmentPattern } from "../../utils/typescript";
67
import { transformAssignment } from "../binary-expression/assignments";
@@ -49,14 +50,20 @@ export function transformForInitializer(
4950
): lua.Identifier {
5051
const valueVariable = lua.createIdentifier("____value");
5152

53+
pushScope(context, ScopeType.LoopInitializer);
54+
5255
if (ts.isVariableDeclarationList(initializer)) {
5356
// Declaration of new variable
5457

5558
const binding = getVariableDeclarationBinding(context, initializer);
5659
if (ts.isArrayBindingPattern(binding) || ts.isObjectBindingPattern(binding)) {
57-
block.statements.unshift(...transformBindingPattern(context, binding, valueVariable));
60+
const [precedingStatements, bindings] = transformInPrecedingStatementScope(context, () =>
61+
transformBindingPattern(context, binding, valueVariable)
62+
);
63+
block.statements.unshift(...precedingStatements, ...bindings);
5864
} else {
5965
// Single variable declared in for loop
66+
popScope(context);
6067
return transformIdentifier(context, binding);
6168
}
6269
} else {
@@ -69,6 +76,7 @@ export function transformForInitializer(
6976
);
7077
}
7178

79+
popScope(context);
7280
return valueVariable;
7381
}
7482

test/unit/destructuring.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ test("in function parameter creates local variables", () => {
5353
expect(code).toContain("local b =");
5454
});
5555

56+
test("in function parameter creates local variables in correct scope", () => {
57+
util.testFunction`
58+
let x = 7;
59+
function foo([x]: [number]) {
60+
x *= 2;
61+
}
62+
foo([1]);
63+
return x;
64+
`.expectToMatchJsResult();
65+
});
66+
5667
test.each(testCases)("in variable declaration (%p)", ({ binding, value }) => {
5768
util.testFunction`
5869
let ${allBindings};

test/unit/loops.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,27 @@ test("forof destructing", () => {
279279
`.expectToMatchJsResult();
280280
});
281281

282+
test("forof destructing scope", () => {
283+
util.testFunction`
284+
let x = 7;
285+
for (let [x] of [[1], [2], [3]]) {
286+
x *= 2;
287+
}
288+
return x;
289+
`.expectToMatchJsResult();
290+
});
291+
292+
// This catches the case where x is falsely seen as globally scoped and the 'local' is stripped out
293+
test("forof destructing scope (global)", () => {
294+
util.testModule`
295+
let x = 7;
296+
for (let [x] of [[1], [2], [3]]) {
297+
x *= 2;
298+
}
299+
if (x !== 7) throw x;
300+
`.expectNoExecutionError();
301+
});
302+
282303
test("forof nested destructing", () => {
283304
util.testFunction`
284305
const obj = { a: [3], b: [5] };

0 commit comments

Comments
 (0)