Skip to content

Commit 4cb55a9

Browse files
committed
Preceding statements proof of concept
1 parent 084e228 commit 4cb55a9

3 files changed

Lines changed: 61 additions & 64 deletions

File tree

src/transformation/context/context.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,44 @@ export class TransformationContext {
7777
return result as lua.Expression;
7878
}
7979

80+
public transformStatement(node: StatementLikeNode): lua.Statement[] {
81+
const transformationResult = this.transformNode(node) as lua.Statement[];
82+
return [...this.popPrecedingStatements(), ...transformationResult];
83+
}
84+
85+
public superTransformStatement(node: StatementLikeNode): lua.Statement[] {
86+
const transformationResult = this.superTransformNode(node) as lua.Statement[];
87+
return [...this.popPrecedingStatements(), ...transformationResult];
88+
}
89+
8090
public transformStatements(node: StatementLikeNode | readonly StatementLikeNode[]): lua.Statement[] {
8191
return Array.isArray(node)
8292
? node.flatMap(n => this.transformStatements(n))
8393
: // TODO: https://github.com/microsoft/TypeScript/pull/28916
84-
(this.transformNode(node as StatementLikeNode) as lua.Statement[]);
94+
this.transformStatement(node as StatementLikeNode);
8595
}
8696

8797
public superTransformStatements(node: StatementLikeNode | readonly StatementLikeNode[]): lua.Statement[] {
8898
return Array.isArray(node)
8999
? node.flatMap(n => this.superTransformStatements(n))
90100
: // TODO: https://github.com/microsoft/TypeScript/pull/28916
91-
(this.superTransformNode(node as StatementLikeNode) as lua.Statement[]);
101+
this.superTransformStatement(node as StatementLikeNode);
102+
}
103+
104+
private identifierCounter = 0;
105+
public createUniqueIdentifier(prefix?: string): lua.Identifier {
106+
return lua.createIdentifier(`____${prefix ? prefix + "_" : ""}var_${this.identifierCounter++}`);
107+
}
108+
109+
private precedingStatementsQueue: lua.Statement[] = [];
110+
public pushPrecedingStatement(...statements: lua.Statement[]): void {
111+
// Reverse because the queue is FIFO
112+
this.precedingStatementsQueue.push(...statements.reverse());
113+
}
114+
115+
public popPrecedingStatements(): lua.Statement[] {
116+
const statements = this.precedingStatementsQueue.reverse();
117+
this.precedingStatementsQueue = [];
118+
return statements;
92119
}
93120
}

src/transformation/visitors/conditional.ts

Lines changed: 22 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,70 +4,31 @@ import { FunctionVisitor, TransformationContext } from "../context";
44
import { performHoisting, popScope, pushScope, ScopeType } from "../utils/scope";
55
import { transformBlockOrStatement } from "./block";
66

7-
function canBeFalsy(context: TransformationContext, type: ts.Type): boolean {
8-
const strictNullChecks = context.options.strict === true || context.options.strictNullChecks === true;
9-
10-
const falsyFlags =
11-
ts.TypeFlags.Boolean |
12-
ts.TypeFlags.BooleanLiteral |
13-
ts.TypeFlags.Undefined |
14-
ts.TypeFlags.Null |
15-
ts.TypeFlags.Never |
16-
ts.TypeFlags.Void |
17-
ts.TypeFlags.Any;
18-
19-
if (type.flags & falsyFlags) {
20-
return true;
21-
} else if (!strictNullChecks && !type.isLiteral()) {
22-
return true;
23-
} else if (type.isUnion()) {
24-
return type.types.some(subType => canBeFalsy(context, subType));
25-
} else {
26-
return false;
27-
}
28-
}
29-
30-
function wrapInFunctionCall(expression: lua.Expression): lua.FunctionExpression {
31-
const returnStatement = lua.createReturnStatement([expression]);
32-
33-
return lua.createFunctionExpression(
34-
lua.createBlock([returnStatement]),
35-
undefined,
36-
undefined,
37-
undefined,
38-
lua.FunctionExpressionFlags.Inline
39-
);
40-
}
41-
42-
function transformProtectedConditionalExpression(
43-
context: TransformationContext,
44-
expression: ts.ConditionalExpression
45-
): lua.CallExpression {
46-
const condition = lua.createParenthesizedExpression(context.transformExpression(expression.condition));
47-
const val1 = context.transformExpression(expression.whenTrue);
48-
const val2 = context.transformExpression(expression.whenFalse);
49-
50-
const val1Function = wrapInFunctionCall(val1);
51-
const val2Function = wrapInFunctionCall(val2);
52-
53-
// (condition and (() => v1) or (() => v2))()
54-
const conditionAnd = lua.createBinaryExpression(condition, val1Function, lua.SyntaxKind.AndOperator);
55-
const orExpression = lua.createBinaryExpression(conditionAnd, val2Function, lua.SyntaxKind.OrOperator);
56-
return lua.createCallExpression(lua.createParenthesizedExpression(orExpression), [], expression);
57-
}
58-
597
export const transformConditionalExpression: FunctionVisitor<ts.ConditionalExpression> = (expression, context) => {
60-
if (canBeFalsy(context, context.checker.getTypeAtLocation(expression.whenTrue))) {
61-
return transformProtectedConditionalExpression(context, expression);
62-
}
8+
// local ____conditional;
9+
const tempVariable = context.createUniqueIdentifier("ternary_conditional");
10+
const tempVariableDeclaration = lua.createVariableDeclarationStatement(tempVariable);
11+
12+
const createTempVariableAssignment = (value: lua.Expression) =>
13+
lua.createBlock([
14+
...context.popPrecedingStatements(),
15+
lua.createAssignmentStatement(lua.cloneIdentifier(tempVariable), value),
16+
]);
17+
18+
// if expression.condition
19+
const ifStatement = lua.createIfStatement(
20+
context.transformExpression(expression.condition),
21+
// then ____conditional = expression.whenTrue
22+
createTempVariableAssignment(context.transformExpression(expression.whenTrue)),
23+
// else ____conditional = expression.whenFalse
24+
createTempVariableAssignment(context.transformExpression(expression.whenFalse))
25+
);
6326

64-
const condition = lua.createParenthesizedExpression(context.transformExpression(expression.condition));
65-
const val1 = context.transformExpression(expression.whenTrue);
66-
const val2 = context.transformExpression(expression.whenFalse);
27+
// Use temp variable declaration and if statement as preceding statements
28+
context.pushPrecedingStatement(tempVariableDeclaration, ifStatement);
6729

68-
// condition and v1 or v2
69-
const conditionAnd = lua.createBinaryExpression(condition, val1, lua.SyntaxKind.AndOperator);
70-
return lua.createBinaryExpression(conditionAnd, val2, lua.SyntaxKind.OrOperator, expression);
30+
// ____conditional
31+
return lua.cloneIdentifier(tempVariable);
7132
};
7233

7334
export function transformIfStatement(statement: ts.IfStatement, context: TransformationContext): lua.IfStatement {

test/unit/conditionals.spec.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ test.each([
304304
{ input: "true ? false : true", options: { luaTarget: tstl.LuaTarget.LuaJIT } },
305305
{ input: "false ? false : true", options: { luaTarget: tstl.LuaTarget.LuaJIT } },
306306
{ input: "true ? undefined : true", options: { luaTarget: tstl.LuaTarget.LuaJIT } },
307-
])("Ternary operator (%p)", ({ input, options }) => {
307+
])("Ternary conditional operator (%p)", ({ input, options }) => {
308308
util.testFunction`
309309
const literalValue = "literal";
310310
let variableValue: string;
@@ -316,6 +316,15 @@ test.each([
316316
.expectToMatchJsResult();
317317
});
318318

319+
test("Nested ternary conditional conditional", () => {
320+
util.testFunction`
321+
const a: number = 4;
322+
return a > 2
323+
? (a === 4 ? "foo" : "bar")
324+
: "baz";
325+
`.expectToMatchJsResult();
326+
});
327+
319328
test.each([
320329
{ condition: true, lhs: 4, rhs: 5 },
321330
{ condition: false, lhs: 4, rhs: 5 },

0 commit comments

Comments
 (0)