Skip to content

Commit e9ce425

Browse files
TheLartianstomblind
authored andcommitted
simplify ternary transpilation (#310)
* simplify ternary transpilation * remove ternary from lualib * remove reference to ternary * replace boxing with IIFE * use boxing ternary in luajit transpiler * add transpilation tests for ternary * transpile unprotected ternary for non falsible whenTrue type * resolve merge conflict * add test for undefined whenTrue * add test for literal and non-literal values * add support for strictNullChecks * handle union types * add tests for strictNullChecks * remove FocusTest * added literal ternary test * remove PossiblyFalsy flag check as it is not applicable to lua * correctly handle null * add Void and Never to falsible falgs * refactor isNonFalsible() -> isFalisble() * check for strict mode
1 parent 4dad0e7 commit e9ce425

File tree

6 files changed

+78
-15
lines changed

6 files changed

+78
-15
lines changed

src/LuaLib.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export enum LuaLibFeature {
2626
StringReplace = "StringReplace",
2727
StringSplit = "StringSplit",
2828
Symbol = "Symbol",
29-
Ternary = "Ternary",
3029
}
3130

3231
const luaLibDependencies: { [lib in LuaLibFeature]?: LuaLibFeature[] } = {

src/TSHelper.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,4 +453,28 @@ export class TSHelper {
453453
public static isDefaultArrayPropertyName(methodName: string): boolean {
454454
return defaultArrayPropertyNames.has(methodName);
455455
}
456+
457+
public static isFalsible(type: ts.Type, strictNullChecks: boolean): boolean {
458+
const falsibleFlags = ts.TypeFlags.Boolean
459+
| ts.TypeFlags.BooleanLiteral
460+
| ts.TypeFlags.Undefined
461+
| ts.TypeFlags.Null
462+
| ts.TypeFlags.Never
463+
| ts.TypeFlags.Void
464+
| ts.TypeFlags.Any;
465+
466+
if (type.flags & falsibleFlags) {
467+
return true;
468+
} else if (!strictNullChecks && !type.isLiteral()) {
469+
return true;
470+
} else if (type.isUnion()) {
471+
for (const subType of type.types) {
472+
if (this.isFalsible(subType, strictNullChecks)) {
473+
return true;
474+
}
475+
}
476+
}
477+
478+
return false;
479+
}
456480
}

src/Transpiler.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ export abstract class LuaTranspiler {
780780
return this.transpileBinaryExpression(node as ts.BinaryExpression, brackets);
781781
case ts.SyntaxKind.ConditionalExpression:
782782
// Add brackets to preserve ordering
783-
return this.transpileConditionalExpression(node as ts.ConditionalExpression, brackets);
783+
return this.transpileConditionalExpression(node as ts.ConditionalExpression);
784784
case ts.SyntaxKind.CallExpression:
785785
return this.transpileCallExpression(node as ts.CallExpression);
786786
case ts.SyntaxKind.PropertyAccessExpression:
@@ -1030,13 +1030,22 @@ export abstract class LuaTranspiler {
10301030
return parts.join("..");
10311031
}
10321032

1033-
public transpileConditionalExpression(node: ts.ConditionalExpression, brackets?: boolean): string {
1033+
public transpileProtectedConditionalExpression(node: ts.ConditionalExpression): string {
10341034
const condition = this.transpileExpression(node.condition);
10351035
const val1 = this.transpileExpression(node.whenTrue);
10361036
const val2 = this.transpileExpression(node.whenFalse);
1037+
return `((${condition}) and function() return ${val1}; end or function() return ${val2}; end)()`;
1038+
}
10371039

1038-
return this.transpileLuaLibFunction(LuaLibFeature.Ternary, condition,
1039-
`function() return ${val1} end`, `function() return ${val2} end`);
1040+
public transpileConditionalExpression(node: ts.ConditionalExpression): string {
1041+
const isStrict = this.options.strict || this.options.strictNullChecks;
1042+
if (tsHelper.isFalsible(this.checker.getTypeAtLocation(node.whenTrue), isStrict)) {
1043+
return this.transpileProtectedConditionalExpression(node);
1044+
}
1045+
const condition = this.transpileExpression(node.condition);
1046+
const val1 = this.transpileExpression(node.whenTrue);
1047+
const val2 = this.transpileExpression(node.whenFalse);
1048+
return `((${condition}) and (${val1}) or (${val2}))`;
10401049
}
10411050

10421051
public transpileBinaryAssignmentExpression(

src/lualib/Ternary.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/targets/Transpiler.JIT.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,12 @@ export class LuaTranspilerJIT extends LuaTranspiler52 {
4444
public transpileSpreadElement(node: ts.SpreadElement): string {
4545
return "unpack(" + this.transpileExpression(node.expression) + ")";
4646
}
47+
48+
/** @override */
49+
public transpileProtectedConditionalExpression(node: ts.ConditionalExpression): string {
50+
const condition = this.transpileExpression(node.condition);
51+
const val1 = this.transpileExpression(node.whenTrue);
52+
const val2 = this.transpileExpression(node.whenFalse);
53+
return `((${condition}) and {${val1}} or {${val2}})[1]`;
54+
}
4755
}

test/unit/expressions.spec.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,36 @@ export class ExpressionTests {
192192
Expect(util.transpileString("undefined")).toBe("nil;");
193193
}
194194

195+
@TestCase("true ? 'a' : 'b'", "a")
196+
@TestCase("false ? 'a' : 'b'", "b")
197+
@TestCase("true ? false : true", false)
198+
@TestCase("false ? false : true", true)
199+
@TestCase("true ? literalValue : true", "literal")
200+
@TestCase("true ? variableValue : true", undefined)
201+
@TestCase("true ? maybeUndefinedValue : true", undefined)
202+
@TestCase("true ? maybeBooleanValue : true", false)
203+
@TestCase("true ? maybeUndefinedValue : true", undefined, { strictNullChecks: true })
204+
@TestCase("true ? maybeBooleanValue : true", false, { strictNullChecks: true })
205+
@TestCase("true ? undefined : true", undefined, { strictNullChecks: true })
206+
@TestCase("true ? null : true", undefined, { strictNullChecks: true })
207+
@TestCase("true ? false : true", false, { luaTarget: LuaTarget.Lua51 })
208+
@TestCase("false ? false : true", true, { luaTarget: LuaTarget.Lua51 })
209+
@TestCase("true ? undefined : true", undefined, { luaTarget: LuaTarget.Lua51 })
210+
@TestCase("true ? false : true", false, { luaTarget: LuaTarget.LuaJIT })
211+
@TestCase("false ? false : true", true, { luaTarget: LuaTarget.LuaJIT })
212+
@TestCase("true ? undefined : true", undefined, { luaTarget: LuaTarget.LuaJIT })
213+
@Test("Ternary operator")
214+
public ternaryOperator(input: string, expected: any, options?: ts.CompilerOptions): void {
215+
const source = `const literalValue = 'literal';`
216+
+ `let variableValue:string;`
217+
+ `let maybeBooleanValue:string|boolean = false;`
218+
+ `let maybeUndefinedValue:string|undefined;`
219+
+ `return ${input};`;
220+
const lua = util.transpileString(source, options);
221+
const result = util.executeLua(lua);
222+
Expect(result).toBe(expected);
223+
}
224+
195225
@TestCase("inst.field", 8)
196226
@TestCase("inst.field + 3", 8 + 3)
197227
@TestCase("inst.field * 3", 8 * 3)
@@ -260,7 +290,7 @@ export class ExpressionTests {
260290
@TestCase("inst.superBaseField", 4)
261291
@Test("Inherited accessors")
262292
public inheritedAccessors(expression: string, expected: any): void {
263-
const source = `class MyBaseClass {`
293+
const source = `class MyBaseClass {`
264294
+ ` public _baseField: number;`
265295
+ ` public get baseField(): number { return this._baseField + 6; }`
266296
+ ` public set baseField(v: number) { this._baseField = v; }`
@@ -269,13 +299,13 @@ export class ExpressionTests {
269299
+ ` public _field: number;`
270300
+ ` public get field(): number { return this._field + 4; }`
271301
+ ` public set field(v: number) { this._field = v; }`
272-
+ `}`
302+
+ `}`
273303
+ `class MySuperClass extends MyClass {`
274304
+ ` public _superField: number;`
275305
+ ` public get superField(): number { return this._superField + 2; }`
276306
+ ` public set superField(v: number) { this._superField = v; }`
277307
+ ` public get superBaseField() { return this.baseField - 3; }`
278-
+ `}`
308+
+ `}`
279309
+ `var inst = new MySuperClass();`
280310
+ `inst.baseField = 1;`
281311
+ `inst.field = 2;`

0 commit comments

Comments
 (0)