Skip to content

Commit b4a2ddd

Browse files
refactor: handle side-effects plus test
1 parent f95902d commit b4a2ddd

3 files changed

Lines changed: 88 additions & 17 deletions

File tree

src/transformation/visitors/switch.ts

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ const containsBreakOrReturn = (statements: ts.Node[]): boolean => {
2121
export const transformSwitchStatement: FunctionVisitor<ts.SwitchStatement> = (statement, context) => {
2222
const scope = pushScope(context, ScopeType.Switch);
2323

24-
// Give the switch a unique name to prevent nested switches from acting up.
24+
// Give the switch and condition accumulator a unique name to prevent nested switches from acting up.
2525
const switchName = `____switch${scope.id}`;
26+
const conditionName = `____cond${scope.id}`;
2627
const switchVariable = lua.createIdentifier(switchName);
28+
const conditionVariable = lua.createIdentifier(conditionName);
2729

2830
// Collect all the expressions into a single expression for use in the default clause
2931
let allExpressions: lua.BinaryExpression;
@@ -62,11 +64,11 @@ export const transformSwitchStatement: FunctionVisitor<ts.SwitchStatement> = (st
6264
// Fallthrough is handled by accepting the last condition as an additional or clause
6365
// Default is the not of all known case expressions
6466
let previousClause: ts.CaseOrDefaultClause;
65-
let condition: lua.Expression;
67+
let condition: lua.Expression | undefined;
6668
statement.caseBlock.clauses.forEach(clause => {
6769
if (!condition || (previousClause && containsBreakOrReturn([...previousClause.statements]))) {
6870
if (ts.isDefaultClause(clause)) {
69-
condition = lua.createUnaryExpression(allExpressions, lua.SyntaxKind.NotOperator);
71+
// If the default is first, or followed by a break, we can't fall into it, skip.
7072
} else {
7173
condition = lua.createBinaryExpression(
7274
switchVariable,
@@ -76,11 +78,7 @@ export const transformSwitchStatement: FunctionVisitor<ts.SwitchStatement> = (st
7678
}
7779
} else {
7880
if (ts.isDefaultClause(clause)) {
79-
condition = lua.createBinaryExpression(
80-
condition,
81-
lua.createUnaryExpression(allExpressions, lua.SyntaxKind.NotOperator),
82-
lua.SyntaxKind.OrOperator
83-
);
81+
// use the previous condition for the default clause
8482
} else {
8583
condition = lua.createBinaryExpression(
8684
condition,
@@ -94,14 +92,50 @@ export const transformSwitchStatement: FunctionVisitor<ts.SwitchStatement> = (st
9492
}
9593
}
9694

97-
if (condition && clause.statements.length) {
95+
if (clause.statements.length) {
96+
if (!ts.isDefaultClause(clause) && condition) {
97+
statements.push(
98+
lua.createAssignmentStatement(
99+
conditionVariable,
100+
lua.createBinaryExpression(conditionVariable, condition, lua.SyntaxKind.OrOperator)
101+
)
102+
);
103+
condition = undefined;
104+
}
105+
98106
statements.push(
99-
lua.createIfStatement(condition, lua.createBlock(context.transformStatements(clause.statements)))
107+
lua.createIfStatement(
108+
conditionVariable,
109+
lua.createBlock(context.transformStatements(clause.statements))
110+
)
100111
);
101112
}
102113

103114
previousClause = clause;
104115
});
116+
117+
// Amalgamate the default w/ fallthrough clauses and execute if nothing else executed above
118+
const start = clauses.findIndex(c => ts.isDefaultClause(c));
119+
if (start >= 0) {
120+
const end = statement.caseBlock.clauses
121+
.slice(start)
122+
.findIndex(clause => containsBreakOrReturn([...clause.statements]));
123+
const defaultStatements = statement.caseBlock.clauses
124+
.slice(start, end >= 0 ? end + start + 1 : undefined)
125+
.reduce<lua.Statement[]>(
126+
(statements, clause) => [...statements, ...context.transformStatements(clause.statements)],
127+
[]
128+
);
129+
130+
if (defaultStatements.length) {
131+
statements.push(
132+
lua.createIfStatement(
133+
lua.createUnaryExpression(conditionVariable, lua.SyntaxKind.NotOperator),
134+
lua.createBlock(defaultStatements)
135+
)
136+
);
137+
}
138+
}
105139
}
106140

107141
// Hoist the variable, function, and import statements to the top of the switch
@@ -110,6 +144,7 @@ export const transformSwitchStatement: FunctionVisitor<ts.SwitchStatement> = (st
110144

111145
// Add the switch expression after hoisting
112146
const expression = context.transformExpression(statement.expression);
147+
statements.unshift(lua.createVariableDeclarationStatement(conditionVariable, lua.createBooleanLiteral(false)));
113148
statements.unshift(lua.createVariableDeclarationStatement(switchVariable, expression));
114149

115150
// Wrap the statements in a repeat until true statement to facilitate dynamic break/returns

test/unit/__snapshots__/switch.spec.ts.snap

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,25 @@ function ____exports.__main(self)
77
local out = {}
88
repeat
99
local ____switch3 = 5
10-
if ((____switch3 == 0) or (____switch3 == 1)) or (____switch3 == 2) then
10+
local ____cond3 = false
11+
____cond3 = ____cond3 or (((____switch3 == 0) or (____switch3 == 1)) or (____switch3 == 2))
12+
if ____cond3 then
1113
__TS__ArrayPush(out, \\"0,1,2\\")
1214
end
13-
if (((____switch3 == 0) or (____switch3 == 1)) or (____switch3 == 2)) or (____switch3 == 3) then
15+
____cond3 = ____cond3 or (____switch3 == 3)
16+
if ____cond3 then
1417
__TS__ArrayPush(out, \\"3\\")
1518
break
1619
end
17-
if not (((((____switch3 == 0) or (____switch3 == 1)) or (____switch3 == 2)) or (____switch3 == 3)) or (____switch3 == 4)) then
20+
if ____cond3 then
1821
__TS__ArrayPush(out, \\"default\\")
1922
end
20-
if (not (((((____switch3 == 0) or (____switch3 == 1)) or (____switch3 == 2)) or (____switch3 == 3)) or (____switch3 == 4))) or (____switch3 == 4) then
23+
____cond3 = ____cond3 or (____switch3 == 4)
24+
if ____cond3 then
25+
__TS__ArrayPush(out, \\"4\\")
26+
end
27+
if not ____cond3 then
28+
__TS__ArrayPush(out, \\"default\\")
2129
__TS__ArrayPush(out, \\"4\\")
2230
end
2331
until true
@@ -32,19 +40,23 @@ function ____exports.__main(self)
3240
local result = -1
3341
repeat
3442
local ____switch3 = 2
35-
if ____switch3 == 0 then
43+
local ____cond3 = false
44+
____cond3 = ____cond3 or (____switch3 == 0)
45+
if ____cond3 then
3646
do
3747
result = 200
3848
break
3949
end
4050
end
41-
if (____switch3 == 0) or (____switch3 == 1) then
51+
____cond3 = ____cond3 or (____switch3 == 1)
52+
if ____cond3 then
4253
do
4354
result = 100
4455
break
4556
end
4657
end
47-
if ((____switch3 == 0) or (____switch3 == 1)) or (____switch3 == 2) then
58+
____cond3 = ____cond3 or (____switch3 == 2)
59+
if ____cond3 then
4860
do
4961
result = 1
5062
break

test/unit/switch.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,3 +399,27 @@ test("switch collapses empty case and minimizes conditions", () => {
399399
.expectLuaToMatchSnapshot()
400400
.expectToMatchJsResult();
401401
});
402+
403+
test("switch handles side-effects", () => {
404+
util.testFunction`
405+
const out = [];
406+
407+
let y = 0;
408+
function foo() {
409+
return y++;
410+
}
411+
412+
let x = 0;
413+
switch (x) {
414+
case foo():
415+
out.push(1);
416+
case foo():
417+
out.push(2);
418+
case foo():
419+
out.push(3);
420+
}
421+
422+
out.push(y);
423+
return out;
424+
`.expectToMatchJsResult();
425+
});

0 commit comments

Comments
 (0)