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
1 change: 1 addition & 0 deletions src/CompilerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export enum LuaTarget {
Lua53 = "5.3",
Lua54 = "5.4",
LuaJIT = "JIT",
Luau = "Luau",
}

export enum BuildMode {
Expand Down
37 changes: 37 additions & 0 deletions src/LuaAST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export enum SyntaxKind {
LabelStatement,
ReturnStatement,
BreakStatement,
ContinueStatement, // Luau only.
ExpressionStatement,

// Expression
Expand All @@ -45,6 +46,7 @@ export enum SyntaxKind {
Identifier,
TableIndexExpression,
ParenthesizedExpression,
ConditionalExpression, // Luau only

// Operators

Expand Down Expand Up @@ -488,6 +490,18 @@ export function createBreakStatement(tsOriginal?: ts.Node): BreakStatement {
return createNode(SyntaxKind.BreakStatement, tsOriginal) as BreakStatement;
}

export interface ContinueStatement extends Statement {
kind: SyntaxKind.ContinueStatement;
}

export function isContinueStatement(node: Node): node is ContinueStatement {
return node.kind === SyntaxKind.ContinueStatement;
}

export function createContinueStatement(tsOriginal?: ts.Node): ContinueStatement {
return createNode(SyntaxKind.ContinueStatement, tsOriginal) as ContinueStatement;
}

export interface ExpressionStatement extends Statement {
kind: SyntaxKind.ExpressionStatement;
expression: Expression;
Expand Down Expand Up @@ -861,3 +875,26 @@ export function createParenthesizedExpression(expression: Expression, tsOriginal
parenthesizedExpression.expression = expression;
return parenthesizedExpression;
}

export type ConditionalExpression = Expression & {
condition: Expression;
whenTrue: Expression;
whenFalse: Expression;
};

export function isConditionalExpression(node: Node): node is ConditionalExpression {
return node.kind === SyntaxKind.ConditionalExpression;
}

export function createConditionalExpression(
condition: Expression,
whenTrue: Expression,
whenFalse: Expression,
tsOriginal?: ts.Node
): ConditionalExpression {
const conditionalExpression = createNode(SyntaxKind.ConditionalExpression, tsOriginal) as ConditionalExpression;
conditionalExpression.condition = condition;
conditionalExpression.whenTrue = whenTrue;
conditionalExpression.whenFalse = whenFalse;
return conditionalExpression;
}
19 changes: 19 additions & 0 deletions src/LuaPrinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ export class LuaPrinter {
return this.printReturnStatement(statement as lua.ReturnStatement);
case lua.SyntaxKind.BreakStatement:
return this.printBreakStatement(statement as lua.BreakStatement);
case lua.SyntaxKind.ContinueStatement:
return this.printContinueStatement(statement as lua.ContinueStatement);
case lua.SyntaxKind.ExpressionStatement:
return this.printExpressionStatement(statement as lua.ExpressionStatement);
default:
Expand Down Expand Up @@ -574,6 +576,10 @@ export class LuaPrinter {
return this.createSourceNode(statement, this.indent("break"));
}

public printContinueStatement(statement: lua.ContinueStatement): SourceNode {
return this.createSourceNode(statement, this.indent("continue"));
}

public printExpressionStatement(statement: lua.ExpressionStatement): SourceNode {
return this.createSourceNode(statement, [this.indent(), this.printExpression(statement.expression)]);
}
Expand Down Expand Up @@ -614,6 +620,8 @@ export class LuaPrinter {
return this.printTableIndexExpression(expression as lua.TableIndexExpression);
case lua.SyntaxKind.ParenthesizedExpression:
return this.printParenthesizedExpression(expression as lua.ParenthesizedExpression);
case lua.SyntaxKind.ConditionalExpression:
return this.printConditionalExpression(expression as lua.ConditionalExpression);
default:
throw new Error(`Tried to print unknown statement kind: ${lua.SyntaxKind[expression.kind]}`);
}
Expand Down Expand Up @@ -828,6 +836,17 @@ export class LuaPrinter {
return this.createSourceNode(expression, ["(", this.printExpression(expression.expression), ")"]);
}

public printConditionalExpression(expression: lua.ConditionalExpression): SourceNode {
return this.createSourceNode(expression, [
"if ",
this.printExpression(expression.condition),
" then ",
this.printExpression(expression.whenTrue),
" else ",
this.printExpression(expression.whenFalse),
]);
}

public printOperator(kind: lua.Operator): SourceNode {
return new SourceNode(null, null, this.relativeSourcePath, LuaPrinter.operatorMap[kind]);
}
Expand Down
1 change: 1 addition & 0 deletions src/transformation/utils/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface FunctionDefinitionInfo {
export enum LoopContinued {
WithGoto,
WithRepeatBreak,
WithContinue,
}

export interface Scope {
Expand Down
20 changes: 14 additions & 6 deletions src/transformation/visitors/break-continue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ export const transformBreakStatement: FunctionVisitor<ts.BreakStatement> = (brea

export const transformContinueStatement: FunctionVisitor<ts.ContinueStatement> = (statement, context) => {
const scope = findScope(context, ScopeType.Loop);
const continuedWith =
context.luaTarget === LuaTarget.Universal ||
context.luaTarget === LuaTarget.Lua50 ||
context.luaTarget === LuaTarget.Lua51
? LoopContinued.WithRepeatBreak
: LoopContinued.WithGoto;

const continuedWith = {
[LuaTarget.Universal]: LoopContinued.WithRepeatBreak,
[LuaTarget.Lua50]: LoopContinued.WithRepeatBreak,
[LuaTarget.Lua51]: LoopContinued.WithRepeatBreak,
[LuaTarget.Lua52]: LoopContinued.WithGoto,
[LuaTarget.Lua53]: LoopContinued.WithGoto,
[LuaTarget.Lua54]: LoopContinued.WithGoto,
[LuaTarget.LuaJIT]: LoopContinued.WithGoto,
[LuaTarget.Luau]: LoopContinued.WithContinue,
}[context.luaTarget];

if (scope) {
scope.loopContinued = continuedWith;
Expand All @@ -28,6 +33,9 @@ export const transformContinueStatement: FunctionVisitor<ts.ContinueStatement> =
case LoopContinued.WithGoto:
return lua.createGotoStatement(label, statement);

case LoopContinued.WithContinue:
return lua.createContinueStatement(statement);

case LoopContinued.WithRepeatBreak:
return [
lua.createAssignmentStatement(lua.createIdentifier(label), lua.createBooleanLiteral(true), statement),
Expand Down
11 changes: 11 additions & 0 deletions src/transformation/visitors/conditional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { performHoisting, ScopeType } from "../utils/scope";
import { transformBlockOrStatement } from "./block";
import { canBeFalsy } from "../utils/typescript";
import { truthyOnlyConditionalValue } from "../utils/diagnostics";
import { LuaTarget } from "../../CompilerOptions";

function transformProtectedConditionalExpression(
context: TransformationContext,
Expand Down Expand Up @@ -38,6 +39,16 @@ function transformProtectedConditionalExpression(
}

export const transformConditionalExpression: FunctionVisitor<ts.ConditionalExpression> = (expression, context) => {
if (context.luaTarget === LuaTarget.Luau) {
// Luau's ternary operator doesn't have these issues
return lua.createConditionalExpression(
context.transformExpression(expression.condition),
context.transformExpression(expression.whenTrue),
context.transformExpression(expression.whenFalse),
expression
);
}

// Check if we need to add diagnostic about Lua truthiness
checkOnlyTruthyCondition(expression.condition, context);

Expand Down
1 change: 1 addition & 0 deletions src/transformation/visitors/loops/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function transformLoopBody(

switch (scope.loopContinued) {
case undefined:
case LoopContinued.WithContinue:
return body;

case LoopContinued.WithGoto:
Expand Down
1 change: 1 addition & 0 deletions test/cli/parse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ describe("tsconfig", () => {
["luaTarget", "5.3", { luaTarget: tstl.LuaTarget.Lua53 }],
["luaTarget", "5.4", { luaTarget: tstl.LuaTarget.Lua54 }],
["luaTarget", "jit", { luaTarget: tstl.LuaTarget.LuaJIT }],
["luaTarget", "luau", { luaTarget: tstl.LuaTarget.Luau }],

["luaBundle", "foo", { luaBundle: "foo" }],
["luaBundleEntry", "bar", { luaBundleEntry: "bar" }],
Expand Down
12 changes: 12 additions & 0 deletions test/translation/__snapshots__/transformation.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Luau Transformation (luauSpecificTransformations) 1`] = `
"t = if true then "is true" else "is false"
while false do
continue
end
repeat
do
continue
end
until not false"
`;

exports[`Transformation (blockScopeVariables) 1`] = `
"do
local a = 1
Expand Down
36 changes: 23 additions & 13 deletions test/translation/transformation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,27 @@ import { annotationDeprecated } from "../../src/transformation/utils/diagnostics
import { couldNotResolveRequire } from "../../src/transpilation/diagnostics";
import * as util from "../util";

const fixturesPath = path.join(__dirname, "./transformation");
const fixtures = fs
.readdirSync(fixturesPath)
.filter(f => path.extname(f) === ".ts")
.sort()
.map(f => [path.parse(f).name, fs.readFileSync(path.join(fixturesPath, f), "utf8")]);
const targetSpecs: Array<[string, tstl.LuaTarget | undefined, string]> = [
["", undefined, "./transformation"],
["Luau ", tstl.LuaTarget.Luau, "./transformation/luau"],
];

test.each(fixtures)("Transformation (%s)", (_name, content) => {
util.testModule(content)
.setOptions({ luaLibImport: tstl.LuaLibImportKind.Require })
.ignoreDiagnostics([annotationDeprecated.code, couldNotResolveRequire.code])
.disableSemanticCheck()
.expectLuaToMatchSnapshot();
});
for (const [name, luaTarget, targetDir] of targetSpecs) {
const fixturesPath = path.join(__dirname, targetDir);
const fixtures = fs
.readdirSync(fixturesPath)
.filter(f => path.extname(f) === ".ts")
.sort()
.map(f => [path.parse(f).name, fs.readFileSync(path.join(fixturesPath, f), "utf8")]);

test.each(fixtures)(`${name}Transformation (%s)`, (_name, content) => {
util.testModule(content)
.setOptions({
luaLibImport: tstl.LuaLibImportKind.Require,
luaTarget,
})
.ignoreDiagnostics([annotationDeprecated.code, couldNotResolveRequire.code])
.disableSemanticCheck()
.expectLuaToMatchSnapshot();
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const t = true ? "is true" : "is false";

while (false) {
continue;
}

do {
continue;
} while (false);
1 change: 1 addition & 0 deletions test/unit/builtins/math.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ util.testEachVersion("Math.atan2", () => util.testExpression`Math.atan2(4, 5)`,
[tstl.LuaTarget.Lua52]: builder => builder.tap(expectMathAtan2),
[tstl.LuaTarget.Lua53]: builder => builder.tap(expectMathAtan),
[tstl.LuaTarget.Lua54]: builder => builder.tap(expectMathAtan2),
[tstl.LuaTarget.Luau]: builder => builder.tap(expectMathAtan2),
});

util.testEachVersion(
Expand Down
1 change: 1 addition & 0 deletions test/unit/loops.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ for (const testCase of [
[tstl.LuaTarget.Lua53]: expectContinueGotoLabel,
[tstl.LuaTarget.Lua54]: expectContinueGotoLabel,
[tstl.LuaTarget.LuaJIT]: expectContinueGotoLabel,
[tstl.LuaTarget.Luau]: () => undefined, // TODO: This is N/A.
Copy link
Member

Choose a reason for hiding this comment

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

Why is this this N/A? Shouldn't this be expecting the Luau continue statement?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That test specifically looks for the label or sentinel variable generated by the code for the other targets which isn't applicable to Luau since it just uses the builtin continue without a need for either.

});
}

Expand Down
2 changes: 2 additions & 0 deletions test/unit/spread.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ describe("in function call", () => {
[tstl.LuaTarget.Lua52]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(),
[tstl.LuaTarget.Lua53]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(),
[tstl.LuaTarget.Lua54]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(),
[tstl.LuaTarget.Luau]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(),
}
);
});
Expand All @@ -94,6 +95,7 @@ describe("in array literal", () => {
[tstl.LuaTarget.Lua52]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(),
[tstl.LuaTarget.Lua53]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(),
[tstl.LuaTarget.Lua54]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(),
[tstl.LuaTarget.Luau]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(),
});

test("of array literal /w OmittedExpression", () => {
Expand Down
1 change: 1 addition & 0 deletions test/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export function expectEachVersionExceptJit<T>(
[tstl.LuaTarget.Lua53]: expectation,
[tstl.LuaTarget.Lua54]: expectation,
[tstl.LuaTarget.LuaJIT]: false, // Exclude JIT
[tstl.LuaTarget.Luau]: false,
};
}

Expand Down
2 changes: 1 addition & 1 deletion tsconfig-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"description": "Specifies the Lua version you want to generate code for.",
"type": "string",
"default": "universal",
"enum": ["5.0", "universal", "5.1", "5.2", "5.3", "5.4", "JIT"]
"enum": ["5.0", "universal", "5.1", "5.2", "5.3", "5.4", "JIT", "Luau"]
},
"noImplicitGlobalVariables": {
"description": "Always declare all root-level variables as local, even if the file is not a module and they would be global in TypeScript.",
Expand Down