X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 14 additions & 19 deletions src/LuaPrinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,16 +273,24 @@ export class LuaPrinter {

protected printStatementArray(statements: lua.Statement[]): SourceChunk[] {
const statementNodes: SourceNode[] = [];
statements = this.removeDeadAndEmptyStatements(statements);
statements.forEach((s, i) => {
const node = this.printStatement(s);
for (const [index, statement] of statements.entries()) {
if (this.isStatementEmpty(statement)) continue;

if (i > 0 && this.statementMayRequireSemiColon(statements[i - 1]) && this.nodeStartsWithParenthesis(node)) {
statementNodes[i - 1].add(";");
const node = this.printStatement(statement);

if (
index > 0 &&
this.statementMayRequireSemiColon(statements[index - 1]) &&
this.nodeStartsWithParenthesis(node)
) {
statementNodes[index - 1].add(";");
}

statementNodes.push(node);
});

if (lua.isReturnStatement(statement)) break;
}

return statementNodes.length > 0 ? [...this.joinChunks("\n", statementNodes), "\n"] : [];
}

Expand Down Expand Up @@ -778,19 +786,6 @@ export class LuaPrinter {
return new SourceNode(null, null, this.sourceFile, LuaPrinter.operatorMap[kind]);
}

protected removeDeadAndEmptyStatements(statements: lua.Statement[]): lua.Statement[] {
const aliveStatements = [];
for (const statement of statements) {
if (!this.isStatementEmpty(statement)) {
aliveStatements.push(statement);
}
if (lua.isReturnStatement(statement)) {
break;
}
}
return aliveStatements;
}

protected isStatementEmpty(statement: lua.Statement): boolean {
return lua.isDoStatement(statement) && (!statement.statements || statement.statements.length === 0);
}
Expand Down
15 changes: 11 additions & 4 deletions src/transformation/utils/lua-ast.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as ts from "typescript";
import * as assert from "assert";
import { LuaTarget } from "../../CompilerOptions";
import * as lua from "../../LuaAST";
import { TransformationContext } from "../context";
Expand Down Expand Up @@ -99,6 +100,8 @@ export function createHoistableVariableDeclarationStatement(
const declaration = lua.createVariableDeclarationStatement(identifier, initializer, tsOriginal);
if (!context.options.noHoisting && identifier.symbolId) {
const scope = peekScope(context);
assert(scope.type !== ScopeType.Switch);

if (!scope.variableDeclarations) {
scope.variableDeclarations = [];
}
Expand Down Expand Up @@ -143,14 +146,16 @@ export function createLocalOrExportedOrGlobalDeclaration(
const insideFunction = findScope(context, ScopeType.Function) !== undefined;

if (context.isModule || getCurrentNamespace(context) || insideFunction || isVariableDeclaration) {
// local
const scope = peekScope(context);

const isPossibleWrappedFunction =
!isFunctionDeclaration &&
tsOriginal &&
ts.isVariableDeclaration(tsOriginal) &&
tsOriginal.initializer &&
isFunctionType(context, context.checker.getTypeAtLocation(tsOriginal.initializer));
if (isPossibleWrappedFunction) {

if (isPossibleWrappedFunction || scope.type === ScopeType.Switch) {
// Split declaration and assignment for wrapped function types to allow recursion
declaration = lua.createVariableDeclarationStatement(lhs, undefined, tsOriginal);
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal);
Expand All @@ -160,13 +165,15 @@ export function createLocalOrExportedOrGlobalDeclaration(

if (!context.options.noHoisting) {
// Remember local variable declarations for hoisting later
const scope = peekScope(context);

if (!scope.variableDeclarations) {
scope.variableDeclarations = [];
}

scope.variableDeclarations.push(declaration);

if (scope.type === ScopeType.Switch) {
declaration = undefined;
}
}
} else if (rhs) {
// global
Expand Down
20 changes: 11 additions & 9 deletions src/transformation/utils/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,14 @@ export function findScope(context: TransformationContext, scopeTypes: ScopeType)
}

const scopeIdCounters = new WeakMap<TransformationContext, number>();
export function pushScope(context: TransformationContext, scopeType: ScopeType): void {
export function pushScope(context: TransformationContext, scopeType: ScopeType): Scope {
const nextScopeId = (scopeIdCounters.get(context) ?? 0) + 1;
scopeIdCounters.set(context, nextScopeId);

const scopeStack = getScopeStack(context);
scopeStack.push({ type: scopeType, id: nextScopeId });
const scope: Scope = { type: scopeType, id: nextScopeId };
scopeStack.push(scope);
return scope;
}

export function popScope(context: TransformationContext): Scope {
Expand Down Expand Up @@ -161,20 +163,20 @@ function hoistVariableDeclarations(
for (const declaration of scope.variableDeclarations) {
const symbols = declaration.left.map(i => i.symbolId).filter(isNonNull);
if (symbols.some(s => shouldHoistSymbol(context, s, scope))) {
let assignment: lua.AssignmentStatement | undefined;
if (declaration.right) {
assignment = lua.createAssignmentStatement(declaration.left, declaration.right);
lua.setNodePosition(assignment, declaration); // Preserve position info for sourcemap
}

const index = result.indexOf(declaration);
assert(index > -1);
if (assignment) {

if (declaration.right) {
const assignment = lua.createAssignmentStatement(declaration.left, declaration.right);
lua.setNodePosition(assignment, declaration); // Preserve position info for sourcemap
result.splice(index, 1, assignment);
} else {
result.splice(index, 1);
}

hoistedLocals.push(...declaration.left);
} else if (scope.type === ScopeType.Switch) {
assert(!declaration.right);
hoistedLocals.push(...declaration.left);
}
}
Expand Down
29 changes: 10 additions & 19 deletions src/transformation/visitors/switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,19 @@ import { LuaTarget } from "../../CompilerOptions";
import * as lua from "../../LuaAST";
import { FunctionVisitor } from "../context";
import { UnsupportedForTarget } from "../utils/errors";
import { peekScope, performHoisting, popScope, pushScope, ScopeType } from "../utils/scope";
import { performHoisting, popScope, pushScope, ScopeType } from "../utils/scope";

export const transformSwitchStatement: FunctionVisitor<ts.SwitchStatement> = (statement, context) => {
if (context.luaTarget === LuaTarget.Lua51) {
throw UnsupportedForTarget("Switch statements", LuaTarget.Lua51, statement);
}

pushScope(context, ScopeType.Switch);

const scope = pushScope(context, ScopeType.Switch);
// Give the switch a unique name to prevent nested switches from acting up.
const scope = peekScope(context);
const switchName = `____switch${scope.id}`;

const expression = context.transformExpression(statement.expression);
const switchVariable = lua.createIdentifier(switchName);
const switchVariableDeclaration = lua.createVariableDeclarationStatement(switchVariable, expression);

let statements: lua.Statement[] = [switchVariableDeclaration];
let statements: lua.Statement[] = [];

const caseClauses = statement.caseBlock.clauses.filter(ts.isCaseClause);
for (const [index, clause] of caseClauses.entries()) {
Expand All @@ -37,25 +32,21 @@ export const transformSwitchStatement: FunctionVisitor<ts.SwitchStatement> = (st
}

const hasDefaultCase = statement.caseBlock.clauses.some(ts.isDefaultClause);
if (hasDefaultCase) {
statements.push(lua.createGotoStatement(`${switchName}_case_default`));
} else {
statements.push(lua.createGotoStatement(`${switchName}_end`));
}
statements.push(lua.createGotoStatement(`${switchName}_${hasDefaultCase ? "case_default" : "end"}`));

for (const [index, clause] of statement.caseBlock.clauses.entries()) {
const label = ts.isCaseClause(clause)
? lua.createLabelStatement(`${switchName}_case_${index}`)
: lua.createLabelStatement(`${switchName}_case_default`);

const body = lua.createDoStatement(context.transformStatements(clause.statements));
statements.push(label, body);
const labelName = `${switchName}_case_${ts.isCaseClause(clause) ? index : "default"}`;
statements.push(lua.createLabelStatement(labelName));
statements.push(lua.createDoStatement(context.transformStatements(clause.statements)));
}

statements.push(lua.createLabelStatement(`${switchName}_end`));

statements = performHoisting(context, statements);
popScope(context);

const expression = context.transformExpression(statement.expression);
statements.unshift(lua.createVariableDeclarationStatement(switchVariable, expression));

return statements;
};
46 changes: 42 additions & 4 deletions test/unit/conditionals.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,46 @@ test.each([0, 1, 2, 3])("nestedSwitch (%p)", inp => {
`.expectToMatchJsResult();
});

test.each([0, 1, 2])("switchLocalScope (%p)", inp => {
test("switch cases scope", () => {
util.testFunction`
switch (0 as number) {
case 0:
let foo: number | undefined = 1;
case 1:
foo = 2;
case 2:
return foo;
}
`.expectToMatchJsResult();
});

test("variable in nested scope does not interfere with case scope", () => {
util.testFunction`
let foo: number = 0;
switch (foo) {
case 0: {
let foo = 1;
}

case 1:
return foo;
}
`.expectToMatchJsResult();
});

test.only("switch using variable re-declared in cases", () => {
util.testFunction`
let foo: number = 0;
switch (foo) {
case 0:
let foo = true;
case 1:
return foo;
}
`.expectToMatchJsResult();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about adding a test case for

let foo: number = 0;
switch (foo) {
    case 0: {
        let foo = true;
    }
    case 1:
        return foo;
}


test.each([0, 1, 2])("switch with block statement scope (%p)", inp => {
util.testFunction`
let result: number = -1;

Expand All @@ -183,8 +222,6 @@ test.each([0, 1, 2])("switchLocalScope (%p)", inp => {

test.each([0, 1, 2, 3])("switchReturn (%p)", inp => {
util.testFunction`
const result: number = -1;

switch (<number>${inp}) {
case 0:
return 0;
Expand All @@ -195,7 +232,8 @@ test.each([0, 1, 2, 3])("switchReturn (%p)", inp => {
return 2;
break;
}
return result;

return -1;
`.expectToMatchJsResult();
});

Expand Down
X Tutup