From 893bfaa4e0f8fcd48aac33483114469eabafe7cc Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 16 Apr 2022 04:17:11 +0000 Subject: [PATCH 01/69] feat: add flag for lua 5.0 target --- src/CompilerOptions.ts | 1 + test/cli/parse.spec.ts | 1 + test/unit/builtins/math.spec.ts | 1 + test/unit/loops.spec.ts | 1 + test/unit/spread.spec.ts | 2 ++ test/util.ts | 1 + 6 files changed, 7 insertions(+) diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index d5023bf3f..8bb37b7db 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -51,6 +51,7 @@ export enum LuaLibImportKind { export enum LuaTarget { Universal = "universal", + Lua50 = "5.0", Lua51 = "5.1", Lua52 = "5.2", Lua53 = "5.3", diff --git a/test/cli/parse.spec.ts b/test/cli/parse.spec.ts index 921af9c45..d027e33b5 100644 --- a/test/cli/parse.spec.ts +++ b/test/cli/parse.spec.ts @@ -228,6 +228,7 @@ describe("tsconfig", () => { ["luaLibImport", "require", { luaLibImport: tstl.LuaLibImportKind.Require }], ["luaTarget", "universal", { luaTarget: tstl.LuaTarget.Universal }], + ["luaTarget", "5.0", { luaTarget: tstl.LuaTarget.Lua50 }], ["luaTarget", "5.1", { luaTarget: tstl.LuaTarget.Lua51 }], ["luaTarget", "5.2", { luaTarget: tstl.LuaTarget.Lua52 }], ["luaTarget", "5.3", { luaTarget: tstl.LuaTarget.Lua53 }], diff --git a/test/unit/builtins/math.spec.ts b/test/unit/builtins/math.spec.ts index 9e191e1e2..3126a85ca 100644 --- a/test/unit/builtins/math.spec.ts +++ b/test/unit/builtins/math.spec.ts @@ -78,6 +78,7 @@ const expectLualibMathAtan2: util.TapCallback = builder => util.testEachVersion("Math.atan2", () => util.testExpression`Math.atan2(4, 5)`, { [tstl.LuaTarget.Universal]: builder => builder.tap(expectLualibMathAtan2), [tstl.LuaTarget.LuaJIT]: builder => builder.tap(expectMathAtan2), + [tstl.LuaTarget.Lua50]: builder => builder.tap(expectMathAtan2), [tstl.LuaTarget.Lua51]: builder => builder.tap(expectMathAtan2), [tstl.LuaTarget.Lua52]: builder => builder.tap(expectMathAtan2), [tstl.LuaTarget.Lua53]: builder => builder.tap(expectMathAtan), diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index 427af9d06..0fff55d9d 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -520,6 +520,7 @@ for (const testCase of [ util.testEachVersion(`loop continue (${testCase})`, () => util.testModule(testCase), { [tstl.LuaTarget.Universal]: builder => builder.expectDiagnosticsToMatchSnapshot([unsupportedForTarget.code]), + [tstl.LuaTarget.Lua50]: builder => builder.expectDiagnosticsToMatchSnapshot([unsupportedForTarget.code]), [tstl.LuaTarget.Lua51]: builder => builder.expectDiagnosticsToMatchSnapshot([unsupportedForTarget.code]), [tstl.LuaTarget.Lua52]: expectContinueGotoLabel, [tstl.LuaTarget.Lua53]: expectContinueGotoLabel, diff --git a/test/unit/spread.spec.ts b/test/unit/spread.spec.ts index c3fe72b07..a88343324 100644 --- a/test/unit/spread.spec.ts +++ b/test/unit/spread.spec.ts @@ -76,6 +76,7 @@ describe("in function call", () => { { [tstl.LuaTarget.Universal]: builder => builder.tap(expectLualibUnpack).expectToMatchJsResult(), [tstl.LuaTarget.LuaJIT]: builder => builder.tap(expectUnpack), + [tstl.LuaTarget.Lua50]: builder => builder.tap(expectUnpack).expectToMatchJsResult(), [tstl.LuaTarget.Lua51]: builder => builder.tap(expectUnpack).expectToMatchJsResult(), [tstl.LuaTarget.Lua52]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(), [tstl.LuaTarget.Lua53]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(), @@ -88,6 +89,7 @@ describe("in array literal", () => { util.testEachVersion(undefined, () => util.testExpression`[...[0, 1, 2]]`, { [tstl.LuaTarget.Universal]: builder => builder.tap(expectLualibUnpack).expectToMatchJsResult(), [tstl.LuaTarget.LuaJIT]: builder => builder.tap(expectUnpack), + [tstl.LuaTarget.Lua50]: builder => builder.tap(expectUnpack).expectToMatchJsResult(), [tstl.LuaTarget.Lua51]: builder => builder.tap(expectUnpack).expectToMatchJsResult(), [tstl.LuaTarget.Lua52]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(), [tstl.LuaTarget.Lua53]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(), diff --git a/test/util.ts b/test/util.ts index 0f053d9a0..5d8af8f85 100644 --- a/test/util.ts +++ b/test/util.ts @@ -70,6 +70,7 @@ export function expectEachVersionExceptJit( ): Record void) | boolean> { return { [tstl.LuaTarget.Universal]: expectation, + [tstl.LuaTarget.Lua50]: expectation, [tstl.LuaTarget.Lua51]: expectation, [tstl.LuaTarget.Lua52]: expectation, [tstl.LuaTarget.Lua53]: expectation, From ceb7e96958e861dba0d76cefa2ed7ea6e928f164 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 16 Apr 2022 04:18:22 +0000 Subject: [PATCH 02/69] feat(5.0): implement spread arguments --- src/LuaAST.ts | 16 +++++++++++++++- src/LuaPrinter.ts | 6 ++++++ src/transformation/visitors/function.ts | 8 +++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/LuaAST.ts b/src/LuaAST.ts index 472831451..698534d9e 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -32,6 +32,7 @@ export enum SyntaxKind { NumericLiteral, NilKeyword, DotsKeyword, + ArgKeyword, TrueKeyword, FalseKeyword, FunctionExpression, @@ -544,6 +545,18 @@ export function createDotsLiteral(tsOriginal?: ts.Node): DotsLiteral { return createNode(SyntaxKind.DotsKeyword, tsOriginal) as DotsLiteral; } +export interface ArgLiteral extends Expression { + kind: SyntaxKind.ArgKeyword; +} + +export function isArgLiteral(node: Node): node is ArgLiteral { + return node.kind === SyntaxKind.ArgKeyword; +} + +export function createArgLiteral(tsOriginal?: ts.Node): ArgLiteral { + return createNode(SyntaxKind.ArgKeyword, tsOriginal) as ArgLiteral; +} + // StringLiteral / NumberLiteral // TODO TS uses the export interface "LiteralLikeNode" with a "text: string" member // but since we don't parse from text I think we can simplify by just having a value member @@ -581,10 +594,11 @@ export function createStringLiteral(value: string, tsOriginal?: ts.Node): String export function isLiteral( node: Node -): node is NilLiteral | DotsLiteral | BooleanLiteral | NumericLiteral | StringLiteral { +): node is NilLiteral | DotsLiteral | ArgLiteral | BooleanLiteral | NumericLiteral | StringLiteral { return ( isNilLiteral(node) || isDotsLiteral(node) || + isArgLiteral(node) || isBooleanLiteral(node) || isNumericLiteral(node) || isStringLiteral(node) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index ed6163dc0..00b654a4a 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -582,6 +582,8 @@ export class LuaPrinter { return this.printNilLiteral(expression as lua.NilLiteral); case lua.SyntaxKind.DotsKeyword: return this.printDotsLiteral(expression as lua.DotsLiteral); + case lua.SyntaxKind.ArgKeyword: + return this.printArgLiteral(expression as lua.ArgLiteral); case lua.SyntaxKind.TrueKeyword: case lua.SyntaxKind.FalseKeyword: return this.printBooleanLiteral(expression as lua.BooleanLiteral); @@ -624,6 +626,10 @@ export class LuaPrinter { return this.createSourceNode(expression, "..."); } + public printArgLiteral(expression: lua.ArgLiteral): SourceNode { + return this.createSourceNode(expression, "arg"); + } + public printBooleanLiteral(expression: lua.BooleanLiteral): SourceNode { return this.createSourceNode(expression, expression.kind === lua.SyntaxKind.TrueKeyword ? "true" : "false"); } diff --git a/src/transformation/visitors/function.ts b/src/transformation/visitors/function.ts index 0b432ae84..b13e9f8ce 100644 --- a/src/transformation/visitors/function.ts +++ b/src/transformation/visitors/function.ts @@ -1,4 +1,5 @@ import * as ts from "typescript"; +import { LuaTarget } from "../../CompilerOptions"; import * as lua from "../../LuaAST"; import { assert } from "../../utils"; import { FunctionVisitor, TransformationContext } from "../context"; @@ -142,7 +143,12 @@ export function transformFunctionBodyHeader( // Push spread operator here if (spreadIdentifier && isRestParameterReferenced(spreadIdentifier, bodyScope)) { - const spreadTable = wrapInTable(lua.createDotsLiteral()); + let spreadTable: lua.Expression + if (context.luaTarget === LuaTarget.Lua50) { + spreadTable = lua.createArgLiteral(); + } else { + spreadTable = wrapInTable(lua.createDotsLiteral()); + } headerStatements.push(lua.createVariableDeclarationStatement(spreadIdentifier, spreadTable)); } From 4d17bb5475a99283e6a0fdab0c8733930b3e50d5 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 16 Apr 2022 04:27:19 +0000 Subject: [PATCH 03/69] fix(5.0): in general, cases that apply to 5.1 should also apply to 5.0 --- src/transformation/builtins/function.ts | 6 +++++- src/transformation/utils/lua-ast.ts | 2 +- src/transformation/visitors/binary-expression/bit.ts | 2 ++ src/transformation/visitors/break-continue.ts | 6 +++++- src/transformation/visitors/errors.ts | 7 +++++-- .../visitors/language-extensions/operators.ts | 1 + 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/transformation/builtins/function.ts b/src/transformation/builtins/function.ts index 161660461..e4d880172 100644 --- a/src/transformation/builtins/function.ts +++ b/src/transformation/builtins/function.ts @@ -40,7 +40,11 @@ export function transformFunctionProperty( ): lua.Expression | undefined { switch (node.name.text) { case "length": - if (context.luaTarget === LuaTarget.Lua51 || context.luaTarget === LuaTarget.Universal) { + if ( + context.luaTarget === LuaTarget.Lua50 || + context.luaTarget === LuaTarget.Lua51 || + context.luaTarget === LuaTarget.Universal + ) { context.diagnostics.push(unsupportedForTarget(node, "function.length", LuaTarget.Lua51)); } diff --git a/src/transformation/utils/lua-ast.ts b/src/transformation/utils/lua-ast.ts index b894dba63..c3f33aac1 100644 --- a/src/transformation/utils/lua-ast.ts +++ b/src/transformation/utils/lua-ast.ts @@ -72,7 +72,7 @@ export function createUnpackCall( } const unpack = - context.luaTarget === LuaTarget.Lua51 || context.luaTarget === LuaTarget.LuaJIT + context.luaTarget === LuaTarget.Lua50 || context.luaTarget === LuaTarget.Lua51 || context.luaTarget === LuaTarget.LuaJIT ? lua.createIdentifier("unpack") : lua.createTableIndexExpression(lua.createIdentifier("table"), lua.createStringLiteral("unpack")); diff --git a/src/transformation/visitors/binary-expression/bit.ts b/src/transformation/visitors/binary-expression/bit.ts index e284b2ea0..30416e497 100644 --- a/src/transformation/visitors/binary-expression/bit.ts +++ b/src/transformation/visitors/binary-expression/bit.ts @@ -63,6 +63,7 @@ export function transformBinaryBitOperation( ): lua.Expression { switch (context.luaTarget) { case LuaTarget.Universal: + case LuaTarget.Lua50: case LuaTarget.Lua51: context.diagnostics.push(unsupportedForTarget(node, "Bitwise operations", LuaTarget.Lua51)); @@ -107,6 +108,7 @@ export function transformUnaryBitOperation( ): lua.Expression { switch (context.luaTarget) { case LuaTarget.Universal: + case LuaTarget.Lua50: case LuaTarget.Lua51: context.diagnostics.push(unsupportedForTarget(node, "Bitwise operations", LuaTarget.Lua51)); diff --git a/src/transformation/visitors/break-continue.ts b/src/transformation/visitors/break-continue.ts index 9e476b17e..559500b11 100644 --- a/src/transformation/visitors/break-continue.ts +++ b/src/transformation/visitors/break-continue.ts @@ -11,7 +11,11 @@ export const transformBreakStatement: FunctionVisitor = (brea }; export const transformContinueStatement: FunctionVisitor = (statement, context) => { - if (context.luaTarget === LuaTarget.Universal || context.luaTarget === LuaTarget.Lua51) { + if ( + context.luaTarget === LuaTarget.Universal || + context.luaTarget === LuaTarget.Lua50 || + context.luaTarget === LuaTarget.Lua51 + ) { context.diagnostics.push(unsupportedForTarget(statement, "Continue statement", LuaTarget.Lua51)); } diff --git a/src/transformation/visitors/errors.ts b/src/transformation/visitors/errors.ts index 894ba3ad5..c6340fd1c 100644 --- a/src/transformation/visitors/errors.ts +++ b/src/transformation/visitors/errors.ts @@ -15,7 +15,7 @@ export const transformTryStatement: FunctionVisitor = (statemen const [tryBlock, tryScope] = transformScopeBlock(context, statement.tryBlock, ScopeType.Try); if ( - context.options.luaTarget === LuaTarget.Lua51 && + (context.options.luaTarget === LuaTarget.Lua50 || context.options.luaTarget === LuaTarget.Lua51) && isInAsyncFunction(statement) && !context.options.lua51AllowTryCatchInAsyncAwait ) { @@ -30,7 +30,10 @@ export const transformTryStatement: FunctionVisitor = (statemen return tryBlock.statements; } - if (context.options.luaTarget === LuaTarget.Lua51 && isInGeneratorFunction(statement)) { + if ( + (context.options.luaTarget === LuaTarget.Lua50 || context.options.luaTarget === LuaTarget.Lua51) && + isInGeneratorFunction(statement) + ) { context.diagnostics.push( unsupportedForTarget(statement, "try/catch inside generator functions", LuaTarget.Lua51) ); diff --git a/src/transformation/visitors/language-extensions/operators.ts b/src/transformation/visitors/language-extensions/operators.ts index dce4f5017..fe419ed8b 100644 --- a/src/transformation/visitors/language-extensions/operators.ts +++ b/src/transformation/visitors/language-extensions/operators.ts @@ -93,6 +93,7 @@ export function transformOperatorMappingExpression( } const isBefore53 = + context.luaTarget === LuaTarget.Lua50 || context.luaTarget === LuaTarget.Lua51 || context.luaTarget === LuaTarget.Lua52 || context.luaTarget === LuaTarget.LuaJIT || From fdb322c29a1d9e87aea157d9d70735f085e62b1e Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 16 Apr 2022 04:33:17 +0000 Subject: [PATCH 04/69] style: run prettier --- src/transformation/utils/lua-ast.ts | 4 +++- src/transformation/visitors/function.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/transformation/utils/lua-ast.ts b/src/transformation/utils/lua-ast.ts index c3f33aac1..023af7cd3 100644 --- a/src/transformation/utils/lua-ast.ts +++ b/src/transformation/utils/lua-ast.ts @@ -72,7 +72,9 @@ export function createUnpackCall( } const unpack = - context.luaTarget === LuaTarget.Lua50 || context.luaTarget === LuaTarget.Lua51 || context.luaTarget === LuaTarget.LuaJIT + context.luaTarget === LuaTarget.Lua50 || + context.luaTarget === LuaTarget.Lua51 || + context.luaTarget === LuaTarget.LuaJIT ? lua.createIdentifier("unpack") : lua.createTableIndexExpression(lua.createIdentifier("table"), lua.createStringLiteral("unpack")); diff --git a/src/transformation/visitors/function.ts b/src/transformation/visitors/function.ts index b13e9f8ce..c5412a43c 100644 --- a/src/transformation/visitors/function.ts +++ b/src/transformation/visitors/function.ts @@ -143,7 +143,7 @@ export function transformFunctionBodyHeader( // Push spread operator here if (spreadIdentifier && isRestParameterReferenced(spreadIdentifier, bodyScope)) { - let spreadTable: lua.Expression + let spreadTable: lua.Expression; if (context.luaTarget === LuaTarget.Lua50) { spreadTable = lua.createArgLiteral(); } else { From 14f9421dbd916bfea70db50626e8ed15769f9c0b Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 16 Apr 2022 18:11:25 +0000 Subject: [PATCH 05/69] test(5.0): share the expected results of the obvious cases with 5.1 --- .../__snapshots__/expressions.spec.ts.snap | 110 ++++++++++++++++++ test/unit/__snapshots__/loops.spec.ts.snap | 59 ++++++++++ test/unit/builtins/async-await.spec.ts | 6 + test/unit/expressions.spec.ts | 8 ++ .../__snapshots__/functions.spec.ts.snap | 12 ++ test/unit/functions/functions.spec.ts | 13 ++- test/unit/functions/generators.spec.ts | 1 + .../__snapshots__/operators.spec.ts.snap | 28 +++++ .../language-extensions/operators.spec.ts | 2 +- 9 files changed, 233 insertions(+), 6 deletions(-) diff --git a/test/unit/__snapshots__/expressions.spec.ts.snap b/test/unit/__snapshots__/expressions.spec.ts.snap index 5f58cde14..c7f016937 100644 --- a/test/unit/__snapshots__/expressions.spec.ts.snap +++ b/test/unit/__snapshots__/expressions.spec.ts.snap @@ -36,6 +36,116 @@ ____exports.__result = 10 - (4 + 5) return ____exports" `; +exports[`Bitop [5.0] ("~a"): code 1`] = ` +"local ____exports = {} +____exports.__result = bit.bnot(a) +return ____exports" +`; + +exports[`Bitop [5.0] ("~a"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; + +exports[`Bitop [5.0] ("a&=b"): code 1`] = ` +"local ____exports = {} +a = bit.band(a, b) +____exports.__result = a +return ____exports" +`; + +exports[`Bitop [5.0] ("a&=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; + +exports[`Bitop [5.0] ("a&b"): code 1`] = ` +"local ____exports = {} +____exports.__result = bit.band(a, b) +return ____exports" +`; + +exports[`Bitop [5.0] ("a&b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; + +exports[`Bitop [5.0] ("a<<=b"): code 1`] = ` +"local ____exports = {} +a = bit.lshift(a, b) +____exports.__result = a +return ____exports" +`; + +exports[`Bitop [5.0] ("a<<=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; + +exports[`Bitop [5.0] ("a<>=b"): code 1`] = ` +"local ____exports = {} +a = bit.arshift(a, b) +____exports.__result = a +return ____exports" +`; + +exports[`Bitop [5.0] ("a>>=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; + +exports[`Bitop [5.0] ("a>>>=b"): code 1`] = ` +"local ____exports = {} +a = bit.rshift(a, b) +____exports.__result = a +return ____exports" +`; + +exports[`Bitop [5.0] ("a>>>=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; + +exports[`Bitop [5.0] ("a>>>b"): code 1`] = ` +"local ____exports = {} +____exports.__result = bit.rshift(a, b) +return ____exports" +`; + +exports[`Bitop [5.0] ("a>>>b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; + +exports[`Bitop [5.0] ("a>>b"): code 1`] = ` +"local ____exports = {} +____exports.__result = bit.arshift(a, b) +return ____exports" +`; + +exports[`Bitop [5.0] ("a>>b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; + +exports[`Bitop [5.0] ("a^=b"): code 1`] = ` +"local ____exports = {} +a = bit.bxor(a, b) +____exports.__result = a +return ____exports" +`; + +exports[`Bitop [5.0] ("a^=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; + +exports[`Bitop [5.0] ("a^b"): code 1`] = ` +"local ____exports = {} +____exports.__result = bit.bxor(a, b) +return ____exports" +`; + +exports[`Bitop [5.0] ("a^b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; + +exports[`Bitop [5.0] ("a|=b"): code 1`] = ` +"local ____exports = {} +a = bit.bor(a, b) +____exports.__result = a +return ____exports" +`; + +exports[`Bitop [5.0] ("a|=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; + +exports[`Bitop [5.0] ("a|b"): code 1`] = ` +"local ____exports = {} +____exports.__result = bit.bor(a, b) +return ____exports" +`; + +exports[`Bitop [5.0] ("a|b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; + exports[`Bitop [5.1] ("~a"): code 1`] = ` "local ____exports = {} ____exports.__result = bit.bnot(a) diff --git a/test/unit/__snapshots__/loops.spec.ts.snap b/test/unit/__snapshots__/loops.spec.ts.snap index e9b6be991..bf8e11c26 100644 --- a/test/unit/__snapshots__/loops.spec.ts.snap +++ b/test/unit/__snapshots__/loops.spec.ts.snap @@ -12,6 +12,19 @@ return ____exports" exports[`forin[Array]: diagnostics 1`] = `"main.ts(3,9): error TSTL: Iterating over arrays with 'for ... in' is not allowed."`; +exports[`loop continue (do { continue; } while (false)) [5.0]: code 1`] = ` +"repeat + do + do + goto __continue2 + end + ::__continue2:: + end +until not false" +`; + +exports[`loop continue (do { continue; } while (false)) [5.0]: diagnostics 1`] = `"main.ts(1,6): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; + exports[`loop continue (do { continue; } while (false)) [5.1]: code 1`] = ` "repeat do @@ -38,6 +51,19 @@ until not false" exports[`loop continue (do { continue; } while (false)) [universal]: diagnostics 1`] = `"main.ts(1,6): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (for (;;) { continue; }) [5.0]: code 1`] = ` +"do + while true do + do + goto __continue2 + end + ::__continue2:: + end +end" +`; + +exports[`loop continue (for (;;) { continue; }) [5.0]: diagnostics 1`] = `"main.ts(1,12): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; + exports[`loop continue (for (;;) { continue; }) [5.1]: code 1`] = ` "do while true do @@ -64,6 +90,17 @@ end" exports[`loop continue (for (;;) { continue; }) [universal]: diagnostics 1`] = `"main.ts(1,12): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (for (const a in {}) { continue; }) [5.0]: code 1`] = ` +"for a in pairs({}) do + do + goto __continue2 + end + ::__continue2:: +end" +`; + +exports[`loop continue (for (const a in {}) { continue; }) [5.0]: diagnostics 1`] = `"main.ts(1,23): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; + exports[`loop continue (for (const a in {}) { continue; }) [5.1]: code 1`] = ` "for a in pairs({}) do do @@ -86,6 +123,17 @@ end" exports[`loop continue (for (const a in {}) { continue; }) [universal]: diagnostics 1`] = `"main.ts(1,23): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (for (const a of []) { continue; }) [5.0]: code 1`] = ` +"for ____, a in ipairs({}) do + do + goto __continue2 + end + ::__continue2:: +end" +`; + +exports[`loop continue (for (const a of []) { continue; }) [5.0]: diagnostics 1`] = `"main.ts(1,23): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; + exports[`loop continue (for (const a of []) { continue; }) [5.1]: code 1`] = ` "for ____, a in ipairs({}) do do @@ -108,6 +156,17 @@ end" exports[`loop continue (for (const a of []) { continue; }) [universal]: diagnostics 1`] = `"main.ts(1,23): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (while (false) { continue; }) [5.0]: code 1`] = ` +"while false do + do + goto __continue2 + end + ::__continue2:: +end" +`; + +exports[`loop continue (while (false) { continue; }) [5.0]: diagnostics 1`] = `"main.ts(1,17): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; + exports[`loop continue (while (false) { continue; }) [5.1]: code 1`] = ` "while false do do diff --git a/test/unit/builtins/async-await.spec.ts b/test/unit/builtins/async-await.spec.ts index 10bf7601f..a06609bd9 100644 --- a/test/unit/builtins/async-await.spec.ts +++ b/test/unit/builtins/async-await.spec.ts @@ -541,6 +541,8 @@ describe("try/catch in async function", () => { // Cannot execute LuaJIT with test runner { ...util.expectEachVersionExceptJit(builder => builder.expectToEqual({ result: 4 })), + [LuaTarget.Lua50]: builder => + builder.expectToHaveDiagnostics([unsupportedForTargetButOverrideAvailable.code]), [LuaTarget.Lua51]: builder => builder.expectToHaveDiagnostics([unsupportedForTargetButOverrideAvailable.code]), } @@ -565,6 +567,8 @@ describe("try/catch in async function", () => { ...util.expectEachVersionExceptJit(builder => builder.expectToEqual({ reason: "an error occurred in the async function: test error" }) ), + [LuaTarget.Lua50]: builder => + builder.expectToHaveDiagnostics([unsupportedForTargetButOverrideAvailable.code]), [LuaTarget.Lua51]: builder => builder.expectToHaveDiagnostics([unsupportedForTargetButOverrideAvailable.code]), } @@ -593,6 +597,8 @@ describe("try/catch in async function", () => { ...util.expectEachVersionExceptJit(builder => builder.expectToEqual({ reason: "an error occurred in the async function: test error" }) ), + [LuaTarget.Lua50]: builder => + builder.expectToHaveDiagnostics([unsupportedForTargetButOverrideAvailable.code]), [LuaTarget.Lua51]: builder => builder.expectToHaveDiagnostics([unsupportedForTargetButOverrideAvailable.code]), } diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 71c014fda..ccc4a471a 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -48,6 +48,14 @@ test.each(["a+=b", "a-=b", "a*=b", "a/=b", "a%=b", "a**=b"])("Binary expressions const supportedInAll = ["~a", "a&b", "a&=b", "a|b", "a|=b", "a^b", "a^=b", "a<>>b", "a>>>=b"]; const unsupportedIn53And54 = ["a>>b", "a>>=b"]; const allBinaryOperators = [...supportedInAll, ...unsupportedIn53And54]; +test.each(allBinaryOperators)("Bitop [5.0] (%p)", input => { + // Bit operations not supported in 5.0, expect an exception + util.testExpression(input) + .setOptions({ luaTarget: tstl.LuaTarget.Lua50, luaLibImport: tstl.LuaLibImportKind.None }) + .disableSemanticCheck() + .expectDiagnosticsToMatchSnapshot([unsupportedForTarget.code]); +}); + test.each(allBinaryOperators)("Bitop [5.1] (%p)", input => { // Bit operations not supported in 5.1, expect an exception util.testExpression(input) diff --git a/test/unit/functions/__snapshots__/functions.spec.ts.snap b/test/unit/functions/__snapshots__/functions.spec.ts.snap index 345e31731..d46de2b6f 100644 --- a/test/unit/functions/__snapshots__/functions.spec.ts.snap +++ b/test/unit/functions/__snapshots__/functions.spec.ts.snap @@ -1,5 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`function.length unsupported ("5.0"): code 1`] = ` +"local ____exports = {} +function ____exports.__main(self) + local function fn(self) + end + return debug.getinfo(fn).nparams - 1 +end +return ____exports" +`; + +exports[`function.length unsupported ("5.0"): diagnostics 1`] = `"main.ts(3,16): error TSTL: function.length is/are not supported for target Lua 5.1."`; + exports[`function.length unsupported ("5.1"): code 1`] = ` "local ____exports = {} function ____exports.__main(self) diff --git a/test/unit/functions/functions.spec.ts b/test/unit/functions/functions.spec.ts index c5a488460..2bc271572 100644 --- a/test/unit/functions/functions.spec.ts +++ b/test/unit/functions/functions.spec.ts @@ -216,14 +216,17 @@ test.each([ `.expectToMatchJsResult(); }); -test.each([tstl.LuaTarget.Lua51, tstl.LuaTarget.Universal])("function.length unsupported (%p)", luaTarget => { - util.testFunction` +test.each([tstl.LuaTarget.Lua50, tstl.LuaTarget.Lua51, tstl.LuaTarget.Universal])( + "function.length unsupported (%p)", + luaTarget => { + util.testFunction` function fn() {} return fn.length; ` - .setOptions({ luaTarget }) - .expectDiagnosticsToMatchSnapshot([unsupportedForTarget.code]); -}); + .setOptions({ luaTarget }) + .expectDiagnosticsToMatchSnapshot([unsupportedForTarget.code]); + } +); test("Recursive function definition", () => { util.testFunction` diff --git a/test/unit/functions/generators.spec.ts b/test/unit/functions/generators.spec.ts index 03b7f3bfd..aa62d719d 100644 --- a/test/unit/functions/generators.spec.ts +++ b/test/unit/functions/generators.spec.ts @@ -165,6 +165,7 @@ util.testEachVersion( // Cannot execute LuaJIT with test runner { ...util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()), + [LuaTarget.Lua50]: builder => builder.expectToHaveDiagnostics([unsupportedForTarget.code]), [LuaTarget.Lua51]: builder => builder.expectToHaveDiagnostics([unsupportedForTarget.code]), } ); diff --git a/test/unit/language-extensions/__snapshots__/operators.spec.ts.snap b/test/unit/language-extensions/__snapshots__/operators.spec.ts.snap index cfeef91c8..b53722a1a 100644 --- a/test/unit/language-extensions/__snapshots__/operators.spec.ts.snap +++ b/test/unit/language-extensions/__snapshots__/operators.spec.ts.snap @@ -20,6 +20,30 @@ exports[`operator mapping - invalid use (declare function foo(op: LuaAddition): void; foo(op);): diagnostics 1`] = `"main.ts(3,82): error TSTL: This function must always be directly called and cannot be referred to."`; +exports[`unsupported binary operator mapping (5.0 LuaBitwiseAnd): code 1`] = `"local ____ = left & right"`; + +exports[`unsupported binary operator mapping (5.0 LuaBitwiseAnd): diagnostics 1`] = `"main.ts(6,9): error TSTL: Native bitwise operations is/are not supported for target Lua 5.0."`; + +exports[`unsupported binary operator mapping (5.0 LuaBitwiseExclusiveOr): code 1`] = `"local ____ = left ~ right"`; + +exports[`unsupported binary operator mapping (5.0 LuaBitwiseExclusiveOr): diagnostics 1`] = `"main.ts(6,9): error TSTL: Native bitwise operations is/are not supported for target Lua 5.0."`; + +exports[`unsupported binary operator mapping (5.0 LuaBitwiseLeftShift): code 1`] = `"local ____ = left << right"`; + +exports[`unsupported binary operator mapping (5.0 LuaBitwiseLeftShift): diagnostics 1`] = `"main.ts(6,9): error TSTL: Native bitwise operations is/are not supported for target Lua 5.0."`; + +exports[`unsupported binary operator mapping (5.0 LuaBitwiseOr): code 1`] = `"local ____ = left | right"`; + +exports[`unsupported binary operator mapping (5.0 LuaBitwiseOr): diagnostics 1`] = `"main.ts(6,9): error TSTL: Native bitwise operations is/are not supported for target Lua 5.0."`; + +exports[`unsupported binary operator mapping (5.0 LuaBitwiseRightShift): code 1`] = `"local ____ = left >> right"`; + +exports[`unsupported binary operator mapping (5.0 LuaBitwiseRightShift): diagnostics 1`] = `"main.ts(6,9): error TSTL: Native bitwise operations is/are not supported for target Lua 5.0."`; + +exports[`unsupported binary operator mapping (5.0 LuaFloorDivision): code 1`] = `"local ____ = left // right"`; + +exports[`unsupported binary operator mapping (5.0 LuaFloorDivision): diagnostics 1`] = `"main.ts(6,9): error TSTL: Floor division operator is/are not supported for target Lua 5.0."`; + exports[`unsupported binary operator mapping (5.1 LuaBitwiseAnd): code 1`] = `"local ____ = left & right"`; exports[`unsupported binary operator mapping (5.1 LuaBitwiseAnd): diagnostics 1`] = `"main.ts(6,9): error TSTL: Native bitwise operations is/are not supported for target Lua 5.1."`; @@ -116,6 +140,10 @@ exports[`unsupported binary operator mapping (universal LuaFloorDivision): code exports[`unsupported binary operator mapping (universal LuaFloorDivision): diagnostics 1`] = `"main.ts(6,9): error TSTL: Floor division operator is/are not supported for target Lua 5.1."`; +exports[`unsupported unary operator mapping (5.0 LuaBitwiseNot): code 1`] = `"local ____ = ~operand"`; + +exports[`unsupported unary operator mapping (5.0 LuaBitwiseNot): diagnostics 1`] = `"main.ts(5,9): error TSTL: Native bitwise operations is/are not supported for target Lua 5.0."`; + exports[`unsupported unary operator mapping (5.1 LuaBitwiseNot): code 1`] = `"local ____ = ~operand"`; exports[`unsupported unary operator mapping (5.1 LuaBitwiseNot): diagnostics 1`] = `"main.ts(5,9): error TSTL: Native bitwise operations is/are not supported for target Lua 5.1."`; diff --git a/test/unit/language-extensions/operators.spec.ts b/test/unit/language-extensions/operators.spec.ts index c84f8aac9..6cc519be9 100644 --- a/test/unit/language-extensions/operators.spec.ts +++ b/test/unit/language-extensions/operators.spec.ts @@ -114,7 +114,7 @@ test.each(binaryMathOperatorTests)( } ); -const luaTargetsPre53 = [LuaTarget.Lua51, LuaTarget.Lua52, LuaTarget.LuaJIT, LuaTarget.Universal]; +const luaTargetsPre53 = [LuaTarget.Lua50, LuaTarget.Lua51, LuaTarget.Lua52, LuaTarget.LuaJIT, LuaTarget.Universal]; const operatorTypesPost53 = [ "LuaFloorDivision", From ff729e3ad41224331940d497bb6d1f452190ac9d Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sun, 17 Apr 2022 06:44:49 +0000 Subject: [PATCH 06/69] feat(5.0): work around the missing '#' and '%' operators --- src/LuaLib.ts | 1 + src/lualib/Modulo.ts | 3 +++ src/transformation/builtins/array.ts | 13 +++++++++++-- src/transformation/builtins/string.ts | 13 +++++++++++-- .../visitors/binary-expression/index.ts | 5 +++++ 5 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 src/lualib/Modulo.ts diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 60ffcf8c7..88dd33040 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -47,6 +47,7 @@ export enum LuaLibFeature { Map = "Map", MathAtan2 = "MathAtan2", MathSign = "MathSign", + Modulo = "Modulo", New = "New", Number = "Number", NumberIsFinite = "NumberIsFinite", diff --git a/src/lualib/Modulo.ts b/src/lualib/Modulo.ts new file mode 100644 index 000000000..2f60cdf82 --- /dev/null +++ b/src/lualib/Modulo.ts @@ -0,0 +1,3 @@ +export function __TS__Modulo(this: void, a: number, b: number): number { + return a - Math.floor(a / b) * b; +} diff --git a/src/transformation/builtins/array.ts b/src/transformation/builtins/array.ts index 9567c45ae..8589e05e7 100644 --- a/src/transformation/builtins/array.ts +++ b/src/transformation/builtins/array.ts @@ -1,4 +1,5 @@ import * as ts from "typescript"; +import { LuaTarget } from "../../CompilerOptions"; import * as lua from "../../LuaAST"; import { TransformationContext } from "../context"; import { unsupportedProperty } from "../utils/diagnostics"; @@ -177,11 +178,19 @@ export function transformArrayPrototypeCall( export function transformArrayProperty( context: TransformationContext, node: ts.PropertyAccessExpression -): lua.UnaryExpression | undefined { +): lua.Expression | undefined { switch (node.name.text) { case "length": const expression = context.transformExpression(node.expression); - return lua.createUnaryExpression(expression, lua.SyntaxKind.LengthOperator, node); + if (context.luaTarget === LuaTarget.Lua50) { + const tableGetn = lua.createTableIndexExpression( + lua.createIdentifier("table"), + lua.createStringLiteral("getn") + ); + return lua.createCallExpression(tableGetn, [expression], node); + } else { + return lua.createUnaryExpression(expression, lua.SyntaxKind.LengthOperator, node); + } default: return undefined; } diff --git a/src/transformation/builtins/string.ts b/src/transformation/builtins/string.ts index d151d8d47..178a529bc 100644 --- a/src/transformation/builtins/string.ts +++ b/src/transformation/builtins/string.ts @@ -1,4 +1,5 @@ import * as ts from "typescript"; +import { LuaTarget } from "../../CompilerOptions"; import * as lua from "../../LuaAST"; import { TransformationContext } from "../context"; import { unsupportedProperty } from "../utils/diagnostics"; @@ -175,11 +176,19 @@ export function transformStringConstructorCall( export function transformStringProperty( context: TransformationContext, node: ts.PropertyAccessExpression -): lua.UnaryExpression | undefined { +): lua.Expression | undefined { switch (node.name.text) { case "length": const expression = context.transformExpression(node.expression); - return lua.createUnaryExpression(expression, lua.SyntaxKind.LengthOperator, node); + if (context.luaTarget === LuaTarget.Lua50) { + const stringLen = lua.createTableIndexExpression( + lua.createIdentifier("string"), + lua.createStringLiteral("len") + ); + return lua.createCallExpression(stringLen, [expression], node); + } else { + return lua.createUnaryExpression(expression, lua.SyntaxKind.LengthOperator, node); + } default: context.diagnostics.push(unsupportedProperty(node.name, "string", node.name.text)); } diff --git a/src/transformation/visitors/binary-expression/index.ts b/src/transformation/visitors/binary-expression/index.ts index ba292d126..442dbbcc2 100644 --- a/src/transformation/visitors/binary-expression/index.ts +++ b/src/transformation/visitors/binary-expression/index.ts @@ -1,4 +1,5 @@ import * as ts from "typescript"; +import { LuaTarget } from "../../../CompilerOptions"; import * as lua from "../../../LuaAST"; import { FunctionVisitor, TransformationContext } from "../../context"; import { wrapInToStringForConcat } from "../../utils/lua-ast"; @@ -68,6 +69,10 @@ function transformBinaryOperationWithNoPrecedingStatements( return transformNullishCoalescingOperationNoPrecedingStatements(context, node, left, right); } + if (operator === ts.SyntaxKind.PercentToken && context.luaTarget === LuaTarget.Lua50) { + return transformLuaLibFunction(context, LuaLibFeature.Modulo, node, left, right); + } + let luaOperator = simpleOperatorsToLua[operator]; // Check if we need to use string concat operator From 635d271841dd637f8ef44f4088c36fdd9df2ed7f Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Wed, 20 Apr 2022 21:00:47 +0000 Subject: [PATCH 07/69] fix: log the correct lua version for unsupported features --- src/transformation/builtins/function.ts | 2 +- src/transformation/utils/diagnostics.ts | 6 ++---- src/transformation/visitors/break-continue.ts | 2 +- test/unit/__snapshots__/loops.spec.ts.snap | 20 +++++++++---------- .../__snapshots__/functions.spec.ts.snap | 4 ++-- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/transformation/builtins/function.ts b/src/transformation/builtins/function.ts index e4d880172..0372a4791 100644 --- a/src/transformation/builtins/function.ts +++ b/src/transformation/builtins/function.ts @@ -45,7 +45,7 @@ export function transformFunctionProperty( context.luaTarget === LuaTarget.Lua51 || context.luaTarget === LuaTarget.Universal ) { - context.diagnostics.push(unsupportedForTarget(node, "function.length", LuaTarget.Lua51)); + context.diagnostics.push(unsupportedForTarget(node, "function.length", context.luaTarget)); } // debug.getinfo(fn) diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index d0ecbd1d1..1f38e8731 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -84,16 +84,14 @@ export const unsupportedRightShiftOperator = createErrorDiagnosticFactory( "Right shift operator is not supported for target Lua 5.3. Use `>>>` instead." ); -type NonUniversalTarget = Exclude; - const getLuaTargetName = (version: LuaTarget) => (version === LuaTarget.LuaJIT ? "LuaJIT" : `Lua ${version}`); export const unsupportedForTarget = createErrorDiagnosticFactory( - (functionality: string, version: NonUniversalTarget) => + (functionality: string, version: LuaTarget) => `${functionality} is/are not supported for target ${getLuaTargetName(version)}.` ); export const unsupportedForTargetButOverrideAvailable = createErrorDiagnosticFactory( - (functionality: string, version: NonUniversalTarget, optionName: keyof TypeScriptToLuaOptions) => + (functionality: string, version: LuaTarget, optionName: keyof TypeScriptToLuaOptions) => `As a precaution, ${functionality} is/are not supported for target ${getLuaTargetName( version )} due to language features/limitations. ` + diff --git a/src/transformation/visitors/break-continue.ts b/src/transformation/visitors/break-continue.ts index 559500b11..580abf73f 100644 --- a/src/transformation/visitors/break-continue.ts +++ b/src/transformation/visitors/break-continue.ts @@ -16,7 +16,7 @@ export const transformContinueStatement: FunctionVisitor = context.luaTarget === LuaTarget.Lua50 || context.luaTarget === LuaTarget.Lua51 ) { - context.diagnostics.push(unsupportedForTarget(statement, "Continue statement", LuaTarget.Lua51)); + context.diagnostics.push(unsupportedForTarget(statement, "Continue statement", context.luaTarget)); } const scope = findScope(context, ScopeType.Loop); diff --git a/test/unit/__snapshots__/loops.spec.ts.snap b/test/unit/__snapshots__/loops.spec.ts.snap index bf8e11c26..7b0c40ad2 100644 --- a/test/unit/__snapshots__/loops.spec.ts.snap +++ b/test/unit/__snapshots__/loops.spec.ts.snap @@ -23,7 +23,7 @@ exports[`loop continue (do { continue; } while (false)) [5.0]: code 1`] = ` until not false" `; -exports[`loop continue (do { continue; } while (false)) [5.0]: diagnostics 1`] = `"main.ts(1,6): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (do { continue; } while (false)) [5.0]: diagnostics 1`] = `"main.ts(1,6): error TSTL: Continue statement is/are not supported for target Lua 5.0."`; exports[`loop continue (do { continue; } while (false)) [5.1]: code 1`] = ` "repeat @@ -49,7 +49,7 @@ exports[`loop continue (do { continue; } while (false)) [universal]: code 1`] = until not false" `; -exports[`loop continue (do { continue; } while (false)) [universal]: diagnostics 1`] = `"main.ts(1,6): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (do { continue; } while (false)) [universal]: diagnostics 1`] = `"main.ts(1,6): error TSTL: Continue statement is/are not supported for target Lua universal."`; exports[`loop continue (for (;;) { continue; }) [5.0]: code 1`] = ` "do @@ -62,7 +62,7 @@ exports[`loop continue (for (;;) { continue; }) [5.0]: code 1`] = ` end" `; -exports[`loop continue (for (;;) { continue; }) [5.0]: diagnostics 1`] = `"main.ts(1,12): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (for (;;) { continue; }) [5.0]: diagnostics 1`] = `"main.ts(1,12): error TSTL: Continue statement is/are not supported for target Lua 5.0."`; exports[`loop continue (for (;;) { continue; }) [5.1]: code 1`] = ` "do @@ -88,7 +88,7 @@ exports[`loop continue (for (;;) { continue; }) [universal]: code 1`] = ` end" `; -exports[`loop continue (for (;;) { continue; }) [universal]: diagnostics 1`] = `"main.ts(1,12): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (for (;;) { continue; }) [universal]: diagnostics 1`] = `"main.ts(1,12): error TSTL: Continue statement is/are not supported for target Lua universal."`; exports[`loop continue (for (const a in {}) { continue; }) [5.0]: code 1`] = ` "for a in pairs({}) do @@ -99,7 +99,7 @@ exports[`loop continue (for (const a in {}) { continue; }) [5.0]: code 1`] = ` end" `; -exports[`loop continue (for (const a in {}) { continue; }) [5.0]: diagnostics 1`] = `"main.ts(1,23): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (for (const a in {}) { continue; }) [5.0]: diagnostics 1`] = `"main.ts(1,23): error TSTL: Continue statement is/are not supported for target Lua 5.0."`; exports[`loop continue (for (const a in {}) { continue; }) [5.1]: code 1`] = ` "for a in pairs({}) do @@ -121,7 +121,7 @@ exports[`loop continue (for (const a in {}) { continue; }) [universal]: code 1`] end" `; -exports[`loop continue (for (const a in {}) { continue; }) [universal]: diagnostics 1`] = `"main.ts(1,23): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (for (const a in {}) { continue; }) [universal]: diagnostics 1`] = `"main.ts(1,23): error TSTL: Continue statement is/are not supported for target Lua universal."`; exports[`loop continue (for (const a of []) { continue; }) [5.0]: code 1`] = ` "for ____, a in ipairs({}) do @@ -132,7 +132,7 @@ exports[`loop continue (for (const a of []) { continue; }) [5.0]: code 1`] = ` end" `; -exports[`loop continue (for (const a of []) { continue; }) [5.0]: diagnostics 1`] = `"main.ts(1,23): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (for (const a of []) { continue; }) [5.0]: diagnostics 1`] = `"main.ts(1,23): error TSTL: Continue statement is/are not supported for target Lua 5.0."`; exports[`loop continue (for (const a of []) { continue; }) [5.1]: code 1`] = ` "for ____, a in ipairs({}) do @@ -154,7 +154,7 @@ exports[`loop continue (for (const a of []) { continue; }) [universal]: code 1`] end" `; -exports[`loop continue (for (const a of []) { continue; }) [universal]: diagnostics 1`] = `"main.ts(1,23): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (for (const a of []) { continue; }) [universal]: diagnostics 1`] = `"main.ts(1,23): error TSTL: Continue statement is/are not supported for target Lua universal."`; exports[`loop continue (while (false) { continue; }) [5.0]: code 1`] = ` "while false do @@ -165,7 +165,7 @@ exports[`loop continue (while (false) { continue; }) [5.0]: code 1`] = ` end" `; -exports[`loop continue (while (false) { continue; }) [5.0]: diagnostics 1`] = `"main.ts(1,17): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (while (false) { continue; }) [5.0]: diagnostics 1`] = `"main.ts(1,17): error TSTL: Continue statement is/are not supported for target Lua 5.0."`; exports[`loop continue (while (false) { continue; }) [5.1]: code 1`] = ` "while false do @@ -187,4 +187,4 @@ exports[`loop continue (while (false) { continue; }) [universal]: code 1`] = ` end" `; -exports[`loop continue (while (false) { continue; }) [universal]: diagnostics 1`] = `"main.ts(1,17): error TSTL: Continue statement is/are not supported for target Lua 5.1."`; +exports[`loop continue (while (false) { continue; }) [universal]: diagnostics 1`] = `"main.ts(1,17): error TSTL: Continue statement is/are not supported for target Lua universal."`; diff --git a/test/unit/functions/__snapshots__/functions.spec.ts.snap b/test/unit/functions/__snapshots__/functions.spec.ts.snap index d46de2b6f..5eb2a85e1 100644 --- a/test/unit/functions/__snapshots__/functions.spec.ts.snap +++ b/test/unit/functions/__snapshots__/functions.spec.ts.snap @@ -10,7 +10,7 @@ end return ____exports" `; -exports[`function.length unsupported ("5.0"): diagnostics 1`] = `"main.ts(3,16): error TSTL: function.length is/are not supported for target Lua 5.1."`; +exports[`function.length unsupported ("5.0"): diagnostics 1`] = `"main.ts(3,16): error TSTL: function.length is/are not supported for target Lua 5.0."`; exports[`function.length unsupported ("5.1"): code 1`] = ` "local ____exports = {} @@ -34,7 +34,7 @@ end return ____exports" `; -exports[`function.length unsupported ("universal"): diagnostics 1`] = `"main.ts(3,16): error TSTL: function.length is/are not supported for target Lua 5.1."`; +exports[`function.length unsupported ("universal"): diagnostics 1`] = `"main.ts(3,16): error TSTL: function.length is/are not supported for target Lua universal."`; exports[`missing declaration name: code 1`] = ` "function ____(self) From 2836859476950fb158ffb0dc4bff9fdefc5eeb24 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Wed, 20 Apr 2022 21:07:33 +0000 Subject: [PATCH 08/69] style: condense spread table if branch --- src/transformation/visitors/function.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/transformation/visitors/function.ts b/src/transformation/visitors/function.ts index c5412a43c..681401b41 100644 --- a/src/transformation/visitors/function.ts +++ b/src/transformation/visitors/function.ts @@ -143,12 +143,8 @@ export function transformFunctionBodyHeader( // Push spread operator here if (spreadIdentifier && isRestParameterReferenced(spreadIdentifier, bodyScope)) { - let spreadTable: lua.Expression; - if (context.luaTarget === LuaTarget.Lua50) { - spreadTable = lua.createArgLiteral(); - } else { - spreadTable = wrapInTable(lua.createDotsLiteral()); - } + const spreadTable = + context.luaTarget === LuaTarget.Lua50 ? lua.createArgLiteral() : wrapInTable(lua.createDotsLiteral()); headerStatements.push(lua.createVariableDeclarationStatement(spreadIdentifier, spreadTable)); } From fac16d27576b541b3f5d3b248999b995e373b8fa Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Wed, 20 Apr 2022 21:56:07 +0000 Subject: [PATCH 09/69] test: add a special case for modulo for 5.0 --- .../__snapshots__/expressions.spec.ts.snap | 6 ++ test/unit/expressions.spec.ts | 60 +++++++++++++++++-- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/test/unit/__snapshots__/expressions.spec.ts.snap b/test/unit/__snapshots__/expressions.spec.ts.snap index c7f016937..59c0ce0d6 100644 --- a/test/unit/__snapshots__/expressions.spec.ts.snap +++ b/test/unit/__snapshots__/expressions.spec.ts.snap @@ -572,6 +572,12 @@ ____exports.__result = nil return ____exports" `; +exports[`Translated binary expressions basic numeric [5.0] ("15%3") 1`] = ` +"local ____exports = {} +____exports.__result = __TS__Modulo(15, 3) +return ____exports" +`; + exports[`Unary expressions basic ("!a") 1`] = ` "local ____exports = {} function ____exports.__main(self) diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index ccc4a471a..cd0805b81 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -18,8 +18,56 @@ test.each([ util.testFunction(input).disableSemanticCheck().expectLuaToMatchSnapshot(); }); -test.each(["3+4", "5-2", "6*3", "6**3", "20/5", "15/10", "15%3"])("Binary expressions basic numeric (%p)", input => { - util.testExpression(input).expectToMatchJsResult(); +const numericSupportedInAll = ["3+4", "5-2", "6*3", "6**3", "20/5", "15/10"]; +const translatedIn50 = ["15%3"]; +const allNumericOperators = [...numericSupportedInAll, ...translatedIn50]; +test.each(numericSupportedInAll)("Binary expressions basic numeric [5.0] (%p)", input => { + util.testExpression(input) + .setOptions({ luaTarget: tstl.LuaTarget.Lua50, luaLibImport: tstl.LuaLibImportKind.None }) + .disableSemanticCheck() + .expectToMatchJsResult(); +}); + +test.each(translatedIn50)("Translated binary expressions basic numeric [5.0] (%p)", input => { + util.testExpression(input) + .setOptions({ luaTarget: tstl.LuaTarget.Lua50, luaLibImport: tstl.LuaLibImportKind.None }) + .disableSemanticCheck() + .expectLuaToMatchSnapshot(); +}); + +test.each(allNumericOperators)("Binary expressions basic numeric [5.1] (%p)", input => { + util.testExpression(input) + .setOptions({ luaTarget: tstl.LuaTarget.Lua51, luaLibImport: tstl.LuaLibImportKind.None }) + .disableSemanticCheck() + .expectToMatchJsResult(); +}); + +test.each(allNumericOperators)("Binary expressions basic numeric [JIT] (%p)", input => { + util.testExpression(input) + .setOptions({ luaTarget: tstl.LuaTarget.LuaJIT, luaLibImport: tstl.LuaLibImportKind.None }) + .disableSemanticCheck() + .expectToMatchJsResult(); +}); + +test.each(allNumericOperators)("Binary expressions basic numeric [5.2] (%p)", input => { + util.testExpression(input) + .setOptions({ luaTarget: tstl.LuaTarget.Lua52, luaLibImport: tstl.LuaLibImportKind.None }) + .disableSemanticCheck() + .expectToMatchJsResult(); +}); + +test.each(allNumericOperators)("Binary expressions basic numeric [5.3] (%p)", input => { + util.testExpression(input) + .setOptions({ luaTarget: tstl.LuaTarget.Lua53, luaLibImport: tstl.LuaLibImportKind.None }) + .disableSemanticCheck() + .expectToMatchJsResult(); +}); + +test.each(allNumericOperators)("Binary expressions basic numeric [5.4] (%p)", input => { + util.testExpression(input) + .setOptions({ luaTarget: tstl.LuaTarget.Lua54, luaLibImport: tstl.LuaLibImportKind.None }) + .disableSemanticCheck() + .expectToMatchJsResult(); }); test.each(["1==1", "1===1", "1!=1", "1!==1", "1>1", "1>=1", "1<1", "1<=1", "1&&1", "1||1"])( @@ -45,9 +93,9 @@ test.each(["a+=b", "a-=b", "a*=b", "a/=b", "a%=b", "a**=b"])("Binary expressions `.expectToMatchJsResult(); }); -const supportedInAll = ["~a", "a&b", "a&=b", "a|b", "a|=b", "a^b", "a^=b", "a<>>b", "a>>>=b"]; +const binarySupportedInAll = ["~a", "a&b", "a&=b", "a|b", "a|=b", "a^b", "a^=b", "a<>>b", "a>>>=b"]; const unsupportedIn53And54 = ["a>>b", "a>>=b"]; -const allBinaryOperators = [...supportedInAll, ...unsupportedIn53And54]; +const allBinaryOperators = [...binarySupportedInAll, ...unsupportedIn53And54]; test.each(allBinaryOperators)("Bitop [5.0] (%p)", input => { // Bit operations not supported in 5.0, expect an exception util.testExpression(input) @@ -78,14 +126,14 @@ test.each(allBinaryOperators)("Bitop [5.2] (%p)", input => { .expectLuaToMatchSnapshot(); }); -test.each(supportedInAll)("Bitop [5.3] (%p)", input => { +test.each(binarySupportedInAll)("Bitop [5.3] (%p)", input => { util.testExpression(input) .setOptions({ luaTarget: tstl.LuaTarget.Lua53, luaLibImport: tstl.LuaLibImportKind.None }) .disableSemanticCheck() .expectLuaToMatchSnapshot(); }); -test.each(supportedInAll)("Bitop [5.4] (%p)", input => { +test.each(binarySupportedInAll)("Bitop [5.4] (%p)", input => { util.testExpression(input) .setOptions({ luaTarget: tstl.LuaTarget.Lua54, luaLibImport: tstl.LuaLibImportKind.None }) .disableSemanticCheck() From bb83e32ed6c3ef230de2071e7e3b9aba7683fad9 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Wed, 20 Apr 2022 21:56:37 +0000 Subject: [PATCH 10/69] style: fix indentation for test function snippet --- test/unit/functions/__snapshots__/functions.spec.ts.snap | 6 +++--- test/unit/functions/functions.spec.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/unit/functions/__snapshots__/functions.spec.ts.snap b/test/unit/functions/__snapshots__/functions.spec.ts.snap index 5eb2a85e1..4e07a4367 100644 --- a/test/unit/functions/__snapshots__/functions.spec.ts.snap +++ b/test/unit/functions/__snapshots__/functions.spec.ts.snap @@ -10,7 +10,7 @@ end return ____exports" `; -exports[`function.length unsupported ("5.0"): diagnostics 1`] = `"main.ts(3,16): error TSTL: function.length is/are not supported for target Lua 5.0."`; +exports[`function.length unsupported ("5.0"): diagnostics 1`] = `"main.ts(3,20): error TSTL: function.length is/are not supported for target Lua 5.0."`; exports[`function.length unsupported ("5.1"): code 1`] = ` "local ____exports = {} @@ -22,7 +22,7 @@ end return ____exports" `; -exports[`function.length unsupported ("5.1"): diagnostics 1`] = `"main.ts(3,16): error TSTL: function.length is/are not supported for target Lua 5.1."`; +exports[`function.length unsupported ("5.1"): diagnostics 1`] = `"main.ts(3,20): error TSTL: function.length is/are not supported for target Lua 5.1."`; exports[`function.length unsupported ("universal"): code 1`] = ` "local ____exports = {} @@ -34,7 +34,7 @@ end return ____exports" `; -exports[`function.length unsupported ("universal"): diagnostics 1`] = `"main.ts(3,16): error TSTL: function.length is/are not supported for target Lua universal."`; +exports[`function.length unsupported ("universal"): diagnostics 1`] = `"main.ts(3,20): error TSTL: function.length is/are not supported for target Lua universal."`; exports[`missing declaration name: code 1`] = ` "function ____(self) diff --git a/test/unit/functions/functions.spec.ts b/test/unit/functions/functions.spec.ts index 2bc271572..a4fa8fc0d 100644 --- a/test/unit/functions/functions.spec.ts +++ b/test/unit/functions/functions.spec.ts @@ -220,9 +220,9 @@ test.each([tstl.LuaTarget.Lua50, tstl.LuaTarget.Lua51, tstl.LuaTarget.Universal] "function.length unsupported (%p)", luaTarget => { util.testFunction` - function fn() {} - return fn.length; - ` + function fn() {} + return fn.length; + ` .setOptions({ luaTarget }) .expectDiagnosticsToMatchSnapshot([unsupportedForTarget.code]); } From 927dba601d366ed84caccc2bc0d9b258a2de2892 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sun, 24 Apr 2022 17:43:47 +0000 Subject: [PATCH 11/69] feat(5.0): implement spread element correctly --- src/transformation/visitors/spread.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/transformation/visitors/spread.ts b/src/transformation/visitors/spread.ts index 1cfbe18ea..9f1454a6f 100644 --- a/src/transformation/visitors/spread.ts +++ b/src/transformation/visitors/spread.ts @@ -1,4 +1,5 @@ import * as ts from "typescript"; +import { LuaTarget } from "../../CompilerOptions"; import * as lua from "../../LuaAST"; import { FunctionVisitor, TransformationContext } from "../context"; import { AnnotationKind, isVarargType } from "../utils/annotations"; @@ -70,7 +71,9 @@ export const transformSpreadElement: FunctionVisitor = (node, } const symbol = context.checker.getSymbolAtLocation(tsInnerExpression); if (symbol && isOptimizedVarArgSpread(context, symbol, tsInnerExpression)) { - return lua.createDotsLiteral(node); + return context.luaTarget === LuaTarget.Lua50 + ? lua.createCallExpression(lua.createIdentifier("unpack"), [lua.createArgLiteral()]) + : lua.createDotsLiteral(node); } } From 64e5f6d9f572ab04cd31895e5486bacaf354152e Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sun, 24 Apr 2022 17:44:55 +0000 Subject: [PATCH 12/69] fix: report correct lua version in bitops diagnostic message --- .../visitors/binary-expression/bit.ts | 4 +-- .../__snapshots__/expressions.spec.ts.snap | 26 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/transformation/visitors/binary-expression/bit.ts b/src/transformation/visitors/binary-expression/bit.ts index 30416e497..14ce4bdef 100644 --- a/src/transformation/visitors/binary-expression/bit.ts +++ b/src/transformation/visitors/binary-expression/bit.ts @@ -65,7 +65,7 @@ export function transformBinaryBitOperation( case LuaTarget.Universal: case LuaTarget.Lua50: case LuaTarget.Lua51: - context.diagnostics.push(unsupportedForTarget(node, "Bitwise operations", LuaTarget.Lua51)); + context.diagnostics.push(unsupportedForTarget(node, "Bitwise operations", context.luaTarget)); case LuaTarget.LuaJIT: return transformBinaryBitLibOperation(node, left, right, operator, "bit"); @@ -110,7 +110,7 @@ export function transformUnaryBitOperation( case LuaTarget.Universal: case LuaTarget.Lua50: case LuaTarget.Lua51: - context.diagnostics.push(unsupportedForTarget(node, "Bitwise operations", LuaTarget.Lua51)); + context.diagnostics.push(unsupportedForTarget(node, "Bitwise operations", context.luaTarget)); case LuaTarget.LuaJIT: return transformUnaryBitLibOperation(node, expression, operator, "bit"); diff --git a/test/unit/__snapshots__/expressions.spec.ts.snap b/test/unit/__snapshots__/expressions.spec.ts.snap index 59c0ce0d6..300dd9828 100644 --- a/test/unit/__snapshots__/expressions.spec.ts.snap +++ b/test/unit/__snapshots__/expressions.spec.ts.snap @@ -42,7 +42,7 @@ ____exports.__result = bit.bnot(a) return ____exports" `; -exports[`Bitop [5.0] ("~a"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; +exports[`Bitop [5.0] ("~a"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.0."`; exports[`Bitop [5.0] ("a&=b"): code 1`] = ` "local ____exports = {} @@ -51,7 +51,7 @@ ____exports.__result = a return ____exports" `; -exports[`Bitop [5.0] ("a&=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; +exports[`Bitop [5.0] ("a&=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.0."`; exports[`Bitop [5.0] ("a&b"): code 1`] = ` "local ____exports = {} @@ -59,7 +59,7 @@ ____exports.__result = bit.band(a, b) return ____exports" `; -exports[`Bitop [5.0] ("a&b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; +exports[`Bitop [5.0] ("a&b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.0."`; exports[`Bitop [5.0] ("a<<=b"): code 1`] = ` "local ____exports = {} @@ -68,7 +68,7 @@ ____exports.__result = a return ____exports" `; -exports[`Bitop [5.0] ("a<<=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; +exports[`Bitop [5.0] ("a<<=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.0."`; exports[`Bitop [5.0] ("a<>=b"): code 1`] = ` "local ____exports = {} @@ -85,7 +85,7 @@ ____exports.__result = a return ____exports" `; -exports[`Bitop [5.0] ("a>>=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; +exports[`Bitop [5.0] ("a>>=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.0."`; exports[`Bitop [5.0] ("a>>>=b"): code 1`] = ` "local ____exports = {} @@ -94,7 +94,7 @@ ____exports.__result = a return ____exports" `; -exports[`Bitop [5.0] ("a>>>=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; +exports[`Bitop [5.0] ("a>>>=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.0."`; exports[`Bitop [5.0] ("a>>>b"): code 1`] = ` "local ____exports = {} @@ -102,7 +102,7 @@ ____exports.__result = bit.rshift(a, b) return ____exports" `; -exports[`Bitop [5.0] ("a>>>b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; +exports[`Bitop [5.0] ("a>>>b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.0."`; exports[`Bitop [5.0] ("a>>b"): code 1`] = ` "local ____exports = {} @@ -110,7 +110,7 @@ ____exports.__result = bit.arshift(a, b) return ____exports" `; -exports[`Bitop [5.0] ("a>>b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; +exports[`Bitop [5.0] ("a>>b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.0."`; exports[`Bitop [5.0] ("a^=b"): code 1`] = ` "local ____exports = {} @@ -119,7 +119,7 @@ ____exports.__result = a return ____exports" `; -exports[`Bitop [5.0] ("a^=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; +exports[`Bitop [5.0] ("a^=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.0."`; exports[`Bitop [5.0] ("a^b"): code 1`] = ` "local ____exports = {} @@ -127,7 +127,7 @@ ____exports.__result = bit.bxor(a, b) return ____exports" `; -exports[`Bitop [5.0] ("a^b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; +exports[`Bitop [5.0] ("a^b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.0."`; exports[`Bitop [5.0] ("a|=b"): code 1`] = ` "local ____exports = {} @@ -136,7 +136,7 @@ ____exports.__result = a return ____exports" `; -exports[`Bitop [5.0] ("a|=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; +exports[`Bitop [5.0] ("a|=b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.0."`; exports[`Bitop [5.0] ("a|b"): code 1`] = ` "local ____exports = {} @@ -144,7 +144,7 @@ ____exports.__result = bit.bor(a, b) return ____exports" `; -exports[`Bitop [5.0] ("a|b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.1."`; +exports[`Bitop [5.0] ("a|b"): diagnostics 1`] = `"main.ts(1,25): error TSTL: Bitwise operations is/are not supported for target Lua 5.0."`; exports[`Bitop [5.1] ("~a"): code 1`] = ` "local ____exports = {} From a5f39f617bae32741ded2f6e08c580f103ae4b94 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 30 Apr 2022 22:12:58 +0000 Subject: [PATCH 13/69] test: refactor spread tests to accomodate 5.0 --- test/unit/__snapshots__/spread.spec.ts.snap | 88 ++++-- test/unit/spread.spec.ts | 320 ++++++++++---------- 2 files changed, 223 insertions(+), 185 deletions(-) diff --git a/test/unit/__snapshots__/spread.spec.ts.snap b/test/unit/__snapshots__/spread.spec.ts.snap index e0ef21233..44731c5c3 100644 --- a/test/unit/__snapshots__/spread.spec.ts.snap +++ b/test/unit/__snapshots__/spread.spec.ts.snap @@ -17,47 +17,56 @@ end return ____exports" `; -exports[`vararg spread optimization With cast 1`] = ` +exports[`vararg spread optimization With cast [5.0] 1`] = ` "local ____exports = {} function ____exports.__main(self) local function pick(self, ...) - local args = {...} + local args = arg return args[2] end local function test(self, ...) - return pick(nil, ...) + return pick( + nil, + unpack(arg) + ) end return test(nil, \\"a\\", \\"b\\", \\"c\\") end return ____exports" `; -exports[`vararg spread optimization basic use 1`] = ` +exports[`vararg spread optimization basic use [5.0] 1`] = ` "local ____exports = {} function ____exports.__main(self) local function pick(self, ...) - local args = {...} + local args = arg return args[2] end local function test(self, ...) - return pick(nil, ...) + return pick( + nil, + unpack(arg) + ) end return test(nil, \\"a\\", \\"b\\", \\"c\\") end return ____exports" `; -exports[`vararg spread optimization block statement 1`] = ` +exports[`vararg spread optimization block statement [5.0] 1`] = ` "local ____exports = {} function ____exports.__main(self) local function pick(self, ...) - local args = {...} + local args = arg return args[2] end local function test(self, ...) local result do - result = pick(nil, ...) + result = pick( + nil, + unpack(arg) + ) end return result end @@ -66,26 +75,32 @@ end return ____exports" `; -exports[`vararg spread optimization body-less arrow function 1`] = ` +exports[`vararg spread optimization body-less arrow function [5.0] 1`] = ` "local ____exports = {} function ____exports.__main(self) local function pick(self, ...) - local args = {...} + local args = arg return args[2] end local function test(____, ...) - return pick(nil, ...) + return pick( + nil, + unpack(arg) + ) end return test(nil, \\"a\\", \\"b\\", \\"c\\") end return ____exports" `; -exports[`vararg spread optimization curry 1`] = ` +exports[`vararg spread optimization curry [5.0] 1`] = ` "local ____exports = {} function ____exports.__main(self) local function test(self, fn, ...) - return fn(nil, ...) + return fn( + nil, + unpack(arg) + ) end return test( nil, @@ -96,27 +111,30 @@ end return ____exports" `; -exports[`vararg spread optimization curry with indirect type 1`] = ` +exports[`vararg spread optimization curry with indirect type [5.0] 1`] = ` "local ____exports = {} function ____exports.__main(self) local function test(self, obj, ...) local fn = obj.fn - return fn(nil, ...) + return fn( + nil, + unpack(arg) + ) end return test( nil, - {fn = function(____, arg) return arg end}, + {fn = function(____, s) return s end}, \\"foobar\\" ) end return ____exports" `; -exports[`vararg spread optimization finally clause 1`] = ` +exports[`vararg spread optimization finally clause [5.0] 1`] = ` "local ____exports = {} function ____exports.__main(self) local function pick(self, ...) - local args = {...} + local args = arg return args[2] end local function test(self, ...) @@ -125,7 +143,10 @@ function ____exports.__main(self) error(\\"foobar\\", 0) end) do - return pick(nil, ...) + return pick( + nil, + unpack(arg) + ) end end end @@ -134,31 +155,37 @@ end return ____exports" `; -exports[`vararg spread optimization function type declared inside scope 1`] = ` +exports[`vararg spread optimization function type declared inside scope [5.0] 1`] = ` "local ____exports = {} function ____exports.__main(self) local function test(self, ...) local function fn(____, ...) - local args = {...} + local args = arg return args[1] end - return fn(nil, ...) + return fn( + nil, + unpack(arg) + ) end test(nil, \\"foobar\\") end return ____exports" `; -exports[`vararg spread optimization if statement 1`] = ` +exports[`vararg spread optimization if statement [5.0] 1`] = ` "local ____exports = {} function ____exports.__main(self) local function pick(self, ...) - local args = {...} + local args = arg return args[2] end local function test(self, ...) if true then - return pick(nil, ...) + return pick( + nil, + unpack(arg) + ) end end return test(nil, \\"a\\", \\"b\\", \\"c\\") @@ -166,17 +193,20 @@ end return ____exports" `; -exports[`vararg spread optimization loop statement 1`] = ` +exports[`vararg spread optimization loop statement [5.0] 1`] = ` "local ____exports = {} function ____exports.__main(self) local function pick(self, ...) - local args = {...} + local args = arg return args[2] end local function test(self, ...) repeat do - return pick(nil, ...) + return pick( + nil, + unpack(arg) + ) end until not false end diff --git a/test/unit/spread.spec.ts b/test/unit/spread.spec.ts index a88343324..bfa0adfbd 100644 --- a/test/unit/spread.spec.ts +++ b/test/unit/spread.spec.ts @@ -4,6 +4,7 @@ import { formatCode } from "../util"; // TODO: Make some utils for testing other targets const expectUnpack: util.TapCallback = builder => expect(builder.getMainLuaCodeChunk()).toMatch(/[^.]unpack\(/); +const expectArg: util.TapCallback = builder => expect(builder.getMainLuaCodeChunk()).toMatch(/ arg/); const expectTableUnpack: util.TapCallback = builder => expect(builder.getMainLuaCodeChunk()).toContain("table.unpack"); const expectLualibUnpack: util.TapCallback = builder => expect(builder.getMainLuaCodeChunk()).toContain("__TS__Unpack"); @@ -76,7 +77,7 @@ describe("in function call", () => { { [tstl.LuaTarget.Universal]: builder => builder.tap(expectLualibUnpack).expectToMatchJsResult(), [tstl.LuaTarget.LuaJIT]: builder => builder.tap(expectUnpack), - [tstl.LuaTarget.Lua50]: builder => builder.tap(expectUnpack).expectToMatchJsResult(), + [tstl.LuaTarget.Lua50]: builder => builder.tap(expectArg).expectToMatchJsResult(), [tstl.LuaTarget.Lua51]: builder => builder.tap(expectUnpack).expectToMatchJsResult(), [tstl.LuaTarget.Lua52]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(), [tstl.LuaTarget.Lua53]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(), @@ -135,58 +136,55 @@ describe("in object literal", () => { }); describe("vararg spread optimization", () => { - test("basic use", () => { - util.testFunction` + util.testEachVersion( + "basic use", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { return pick(...args); } - return test("a", "b", "c"); - ` - .expectLuaToMatchSnapshot() - .expectToMatchJsResult(); - }); + return test("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("body-less arrow function", () => { - util.testFunction` + util.testEachVersion( + "body-less arrow function", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } const test = (...args: string[]) => pick(...args); - return test("a", "b", "c"); - ` - .expectLuaToMatchSnapshot() - .expectToMatchJsResult(); - }); + return test("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("if statement", () => { - util.testFunction` + util.testEachVersion( + "if statement", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { if (true) { return pick(...args); } } - return test("a", "b", "c"); - ` - .expectLuaToMatchSnapshot() - .expectToMatchJsResult(); - }); + return test("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("loop statement", () => { - util.testFunction` + util.testEachVersion( + "loop statement", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { do { return pick(...args); } while (false); } - return test("a", "b", "c"); - ` - .expectLuaToMatchSnapshot() - .expectToMatchJsResult(); - }); + return test("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("block statement", () => { - util.testFunction` + util.testEachVersion( + "block statement", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { let result: string; @@ -195,14 +193,13 @@ describe("vararg spread optimization", () => { } return result; } - return test("a", "b", "c"); - ` - .expectLuaToMatchSnapshot() - .expectToMatchJsResult(); - }); + return test("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("finally clause", () => { - util.testFunction` + util.testEachVersion( + "finally clause", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { try { @@ -212,11 +209,9 @@ describe("vararg spread optimization", () => { return pick(...args); } } - return test("a" ,"b", "c"); - ` - .expectLuaToMatchSnapshot() - .expectToMatchJsResult(); - }); + return test("a" ,"b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); test("$multi", () => { util.testFunction` @@ -233,68 +228,66 @@ describe("vararg spread optimization", () => { .expectToEqual("b"); }); - test("curry", () => { - util.testFunction` + util.testEachVersion( + "curry", + () => util.testFunction` function test(fn: (...args: A) => void, ...args: A) { return fn(...args); } - return test((arg: string) => arg, "foobar"); - ` - .expectLuaToMatchSnapshot() - .expectToMatchJsResult(); - }); + return test((arg: string) => arg, "foobar");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("curry with indirect type", () => { - util.testFunction` + util.testEachVersion( + "curry with indirect type", + () => util.testFunction` function test(obj: {fn: (...args: A) => void}, ...args: A) { const fn = obj.fn; return fn(...args); } - return test({fn: (arg: string) => arg}, "foobar"); - ` - .expectLuaToMatchSnapshot() - .expectToMatchJsResult(); - }); + return test({fn: (arg: string) => arg}, "foobar");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("function type declared inside scope", () => { - util.testFunction` + util.testEachVersion( + "function type declared inside scope", + () => util.testFunction` function test(...args: A) { const fn: (...args: A) => A[0] = (...args) => args[0]; return fn(...args); } - test("foobar"); - ` - .expectLuaToMatchSnapshot() - .expectToMatchJsResult(); - }); + test("foobar");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("With cast", () => { - util.testFunction` + util.testEachVersion( + "With cast", + () => util.testFunction` function pick(...args: any[]) { return args[1]; } function testany>(...args: Parameters) { return pick(...(args as any[])); } - return test<(...args: string[])=>void>("a", "b", "c"); - ` - .expectLuaToMatchSnapshot() - .expectToMatchJsResult(); - }); + return test<(...args: string[])=>void>("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); }); describe("vararg spread de-optimization", () => { - test("array modification", () => { - util.testFunction` + util.testEachVersion( + "array modification", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { args[1] = "foobar"; return pick(...args); } - return test("a", "b", "c"); - `.expectToMatchJsResult(); - }); + return test("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("array modification in hoisted function", () => { - util.testFunction` + util.testEachVersion( + "array modification in hoisted function", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { hoisted(); @@ -302,12 +295,13 @@ describe("vararg spread de-optimization", () => { function hoisted() { args[1] = "foobar"; } return result; } - return test("a", "b", "c"); - `.expectToMatchJsResult(); - }); + return test("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("array modification in secondary hoisted function", () => { - util.testFunction` + util.testEachVersion( + "array modification in secondary hoisted function", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { function triggersHoisted() { hoisted(); } @@ -316,103 +310,112 @@ describe("vararg spread de-optimization", () => { function hoisted() { args[1] = "foobar"; } return result; } - return test("a", "b", "c"); - `.expectToMatchJsResult(); - }); + return test("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); }); describe("vararg spread in IIFE", () => { - test("comma operator", () => { - util.testFunction` + util.testEachVersion( + "comma operator", + () => util.testFunction` function dummy() { return "foobar"; } function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { return (dummy(), pick(...args)); } - return test("a", "b", "c"); - `.expectToMatchJsResult(); - }); + return test("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("assignment expression", () => { - util.testFunction` + util.testEachVersion( + "assignment expression", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { let x: string; return (x = pick(...args)); } - return test("a", "b", "c"); - `.expectToMatchJsResult(); - }); + return test("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("destructured assignment expression", () => { - util.testFunction` + util.testEachVersion( + "destructured assignment expression", + () => util.testFunction` function pick(...args: string[]) { return [args[1]]; } function test(...args: string[]) { let x: string; return ([x] = pick(...args)); } - return test("a", "b", "c"); - `.expectToMatchJsResult(); - }); + return test("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("property-access assignment expression", () => { - util.testFunction` + util.testEachVersion( + "property-access assignment expression", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { let x: {val?: string} = {}; return (x.val = pick(...args)); } - return test("a", "b", "c"); - `.expectToMatchJsResult(); - }); + return test("a", "b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("binary compound assignment", () => { - util.testFunction` + util.testEachVersion( + "binary compound assignment", + () => util.testFunction` function pick(...args: number[]) { return args[1]; } function test(...args: number[]) { let x = 1; return x += pick(...args); } - return test(1, 2, 3); - `.expectToMatchJsResult(); - }); + return test(1, 2, 3);`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("postfix unary compound assignment", () => { - util.testFunction` + util.testEachVersion( + "postfix unary compound assignment", + () => util.testFunction` function pick(...args: number[]) { return args[1]; } function test(...args: number[]) { let x = [7, 8, 9]; return x[pick(...args)]++; } - return test(1, 2, 3); - `.expectToMatchJsResult(); - }); + return test(1, 2, 3);`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("prefix unary compound assignment", () => { - util.testFunction` + util.testEachVersion( + "prefix unary compound assignment", + () => util.testFunction` function pick(...args: number[]) { return args[1]; } function test(...args: number[]) { let x = [7, 8, 9]; return ++x[pick(...args)]; } - return test(1, 2, 3); - `.expectToMatchJsResult(); - }); + return test(1, 2, 3);`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("try clause", () => { - util.testFunction` + util.testEachVersion( + "try clause", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { try { return pick(...args) } catch {} } - return test("a" ,"b", "c"); - `.expectToMatchJsResult(); - }); + return test("a" ,"b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("catch clause", () => { - util.testFunction` + util.testEachVersion( + "catch clause", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { try { @@ -421,62 +424,67 @@ describe("vararg spread in IIFE", () => { return pick(...args) } } - return test("a" ,"b", "c"); - `.expectToMatchJsResult(); - }); + return test("a" ,"b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("class expression", () => { - util.testFunction` + util.testEachVersion( + "class expression", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } function test(...args: string[]) { const fooClass = class Foo { foo = pick(...args); }; return new fooClass().foo; } - return test("a" ,"b", "c"); - `.expectToMatchJsResult(); - }); + return test("a" ,"b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("self-referencing function expression", () => { - util.testFunction` + util.testEachVersion( + "self-referencing function expression", + () => util.testFunction` function pick(...args: string[]) { return args[1]; } const test = function testName(...args: string[]) { return \`\${typeof testName}:\${pick(...args)}\`; } - return test("a" ,"b", "c"); - `.expectToMatchJsResult(); - }); + return test("a" ,"b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("method indirect access (access args)", () => { - util.testFunction` + util.testEachVersion( + "method indirect access (access args)", + () => util.testFunction` const obj = { $method: () => obj.arg, arg: "foobar" }; function getObj(...args: string[]) { obj.arg = args[1]; return obj; } function test(...args: string[]) { return getObj(...args).$method(); } - return test("a" ,"b", "c"); - `.expectToMatchJsResult(); - }); + return test("a" ,"b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("method indirect access (method args)", () => { - util.testFunction` + util.testEachVersion( + "method indirect access (method args)", + () => util.testFunction` const obj = { $pick: (...args: string[]) => args[1] }; function getObj() { return obj; } function test(...args: string[]) { return getObj().$pick(...args); } - return test("a" ,"b", "c"); - `.expectToMatchJsResult(); - }); + return test("a" ,"b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); - test("tagged template method indirect access", () => { - util.testFunction` + util.testEachVersion( + "tagged template method indirect access", + () => util.testFunction` const obj = { $tag: (t: TemplateStringsArray, ...args: string[]) => args[1] }; function getObj() { return obj; } function pick(...args: string[]): string { return args[1]; } function test(...args: string[]) { return getObj().$tag\`FOO\${pick(...args)}BAR\`; } - return test("a" ,"b", "c"); - `.expectToMatchJsResult(); - }); + return test("a" ,"b", "c");`, + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ); }); From 239955452d5825f87d841539e1cb862e75f3682f Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Mon, 2 May 2022 16:37:20 +0000 Subject: [PATCH 14/69] Revert "test: add a special case for modulo for 5.0" This reverts commit fac16d27576b541b3f5d3b248999b995e373b8fa. --- .../__snapshots__/expressions.spec.ts.snap | 6 -- test/unit/expressions.spec.ts | 60 ++----------------- 2 files changed, 6 insertions(+), 60 deletions(-) diff --git a/test/unit/__snapshots__/expressions.spec.ts.snap b/test/unit/__snapshots__/expressions.spec.ts.snap index 300dd9828..999459e57 100644 --- a/test/unit/__snapshots__/expressions.spec.ts.snap +++ b/test/unit/__snapshots__/expressions.spec.ts.snap @@ -572,12 +572,6 @@ ____exports.__result = nil return ____exports" `; -exports[`Translated binary expressions basic numeric [5.0] ("15%3") 1`] = ` -"local ____exports = {} -____exports.__result = __TS__Modulo(15, 3) -return ____exports" -`; - exports[`Unary expressions basic ("!a") 1`] = ` "local ____exports = {} function ____exports.__main(self) diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index cd0805b81..ccc4a471a 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -18,56 +18,8 @@ test.each([ util.testFunction(input).disableSemanticCheck().expectLuaToMatchSnapshot(); }); -const numericSupportedInAll = ["3+4", "5-2", "6*3", "6**3", "20/5", "15/10"]; -const translatedIn50 = ["15%3"]; -const allNumericOperators = [...numericSupportedInAll, ...translatedIn50]; -test.each(numericSupportedInAll)("Binary expressions basic numeric [5.0] (%p)", input => { - util.testExpression(input) - .setOptions({ luaTarget: tstl.LuaTarget.Lua50, luaLibImport: tstl.LuaLibImportKind.None }) - .disableSemanticCheck() - .expectToMatchJsResult(); -}); - -test.each(translatedIn50)("Translated binary expressions basic numeric [5.0] (%p)", input => { - util.testExpression(input) - .setOptions({ luaTarget: tstl.LuaTarget.Lua50, luaLibImport: tstl.LuaLibImportKind.None }) - .disableSemanticCheck() - .expectLuaToMatchSnapshot(); -}); - -test.each(allNumericOperators)("Binary expressions basic numeric [5.1] (%p)", input => { - util.testExpression(input) - .setOptions({ luaTarget: tstl.LuaTarget.Lua51, luaLibImport: tstl.LuaLibImportKind.None }) - .disableSemanticCheck() - .expectToMatchJsResult(); -}); - -test.each(allNumericOperators)("Binary expressions basic numeric [JIT] (%p)", input => { - util.testExpression(input) - .setOptions({ luaTarget: tstl.LuaTarget.LuaJIT, luaLibImport: tstl.LuaLibImportKind.None }) - .disableSemanticCheck() - .expectToMatchJsResult(); -}); - -test.each(allNumericOperators)("Binary expressions basic numeric [5.2] (%p)", input => { - util.testExpression(input) - .setOptions({ luaTarget: tstl.LuaTarget.Lua52, luaLibImport: tstl.LuaLibImportKind.None }) - .disableSemanticCheck() - .expectToMatchJsResult(); -}); - -test.each(allNumericOperators)("Binary expressions basic numeric [5.3] (%p)", input => { - util.testExpression(input) - .setOptions({ luaTarget: tstl.LuaTarget.Lua53, luaLibImport: tstl.LuaLibImportKind.None }) - .disableSemanticCheck() - .expectToMatchJsResult(); -}); - -test.each(allNumericOperators)("Binary expressions basic numeric [5.4] (%p)", input => { - util.testExpression(input) - .setOptions({ luaTarget: tstl.LuaTarget.Lua54, luaLibImport: tstl.LuaLibImportKind.None }) - .disableSemanticCheck() - .expectToMatchJsResult(); +test.each(["3+4", "5-2", "6*3", "6**3", "20/5", "15/10", "15%3"])("Binary expressions basic numeric (%p)", input => { + util.testExpression(input).expectToMatchJsResult(); }); test.each(["1==1", "1===1", "1!=1", "1!==1", "1>1", "1>=1", "1<1", "1<=1", "1&&1", "1||1"])( @@ -93,9 +45,9 @@ test.each(["a+=b", "a-=b", "a*=b", "a/=b", "a%=b", "a**=b"])("Binary expressions `.expectToMatchJsResult(); }); -const binarySupportedInAll = ["~a", "a&b", "a&=b", "a|b", "a|=b", "a^b", "a^=b", "a<>>b", "a>>>=b"]; +const supportedInAll = ["~a", "a&b", "a&=b", "a|b", "a|=b", "a^b", "a^=b", "a<>>b", "a>>>=b"]; const unsupportedIn53And54 = ["a>>b", "a>>=b"]; -const allBinaryOperators = [...binarySupportedInAll, ...unsupportedIn53And54]; +const allBinaryOperators = [...supportedInAll, ...unsupportedIn53And54]; test.each(allBinaryOperators)("Bitop [5.0] (%p)", input => { // Bit operations not supported in 5.0, expect an exception util.testExpression(input) @@ -126,14 +78,14 @@ test.each(allBinaryOperators)("Bitop [5.2] (%p)", input => { .expectLuaToMatchSnapshot(); }); -test.each(binarySupportedInAll)("Bitop [5.3] (%p)", input => { +test.each(supportedInAll)("Bitop [5.3] (%p)", input => { util.testExpression(input) .setOptions({ luaTarget: tstl.LuaTarget.Lua53, luaLibImport: tstl.LuaLibImportKind.None }) .disableSemanticCheck() .expectLuaToMatchSnapshot(); }); -test.each(binarySupportedInAll)("Bitop [5.4] (%p)", input => { +test.each(supportedInAll)("Bitop [5.4] (%p)", input => { util.testExpression(input) .setOptions({ luaTarget: tstl.LuaTarget.Lua54, luaLibImport: tstl.LuaLibImportKind.None }) .disableSemanticCheck() From 555e2f855719e7830d446e857fc58d48dd57b00f Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Mon, 2 May 2022 17:02:36 +0000 Subject: [PATCH 15/69] test: test basic numeric expressions against all versions --- test/unit/expressions.spec.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index ccc4a471a..39237c5d6 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -18,9 +18,13 @@ test.each([ util.testFunction(input).disableSemanticCheck().expectLuaToMatchSnapshot(); }); -test.each(["3+4", "5-2", "6*3", "6**3", "20/5", "15/10", "15%3"])("Binary expressions basic numeric (%p)", input => { - util.testExpression(input).expectToMatchJsResult(); -}); +for (const expression of ["3+4", "5-2", "6*3", "6**3", "20/5", "15/10", "15%3"]) { + util.testEachVersion( + `Binary expressions basic numeric (${expression})`, + () => util.testExpression(expression), + util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) + ) +} test.each(["1==1", "1===1", "1!=1", "1!==1", "1>1", "1>=1", "1<1", "1<=1", "1&&1", "1||1"])( "Binary expressions basic boolean (%p)", From afdc088516657a5c28c101a8ca14f416f0dfc0ed Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Mon, 2 May 2022 17:26:19 +0000 Subject: [PATCH 16/69] style: run prettier --- test/unit/expressions.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 39237c5d6..06aa000b9 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -23,7 +23,7 @@ for (const expression of ["3+4", "5-2", "6*3", "6**3", "20/5", "15/10", "15%3"]) `Binary expressions basic numeric (${expression})`, () => util.testExpression(expression), util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult()) - ) + ); } test.each(["1==1", "1===1", "1!=1", "1!==1", "1>1", "1>=1", "1<1", "1<=1", "1&&1", "1||1"])( From 85db0cb7111c82fe7f0ca072b595f35f567abb05 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 6 May 2022 09:08:01 +0000 Subject: [PATCH 17/69] refactor: use the convenience function to make an unpack call --- src/transformation/visitors/spread.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transformation/visitors/spread.ts b/src/transformation/visitors/spread.ts index 9f1454a6f..6deeff6c0 100644 --- a/src/transformation/visitors/spread.ts +++ b/src/transformation/visitors/spread.ts @@ -72,7 +72,7 @@ export const transformSpreadElement: FunctionVisitor = (node, const symbol = context.checker.getSymbolAtLocation(tsInnerExpression); if (symbol && isOptimizedVarArgSpread(context, symbol, tsInnerExpression)) { return context.luaTarget === LuaTarget.Lua50 - ? lua.createCallExpression(lua.createIdentifier("unpack"), [lua.createArgLiteral()]) + ? createUnpackCall(context, lua.createArgLiteral(), node) : lua.createDotsLiteral(node); } } From 852febd4e2e13f1e0cc99b64671594908d86a75b Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 7 May 2022 06:02:25 +0000 Subject: [PATCH 18/69] style(lualib): use @noselfinfile for the modulo polyfill --- src/lualib/Modulo.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lualib/Modulo.ts b/src/lualib/Modulo.ts index 2f60cdf82..b2f3c0fe8 100644 --- a/src/lualib/Modulo.ts +++ b/src/lualib/Modulo.ts @@ -1,3 +1,5 @@ -export function __TS__Modulo(this: void, a: number, b: number): number { +/** @noSelfInFile */ + +export function __TS__Modulo(a: number, b: number): number { return a - Math.floor(a / b) * b; } From 1f60fa3b6c6d01130dfeb666a1e674bc5f8ffe79 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Tue, 10 May 2022 05:46:18 +0000 Subject: [PATCH 19/69] fix: make bundling compatible with lua 5.0 --- src/transpilation/bundle.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/transpilation/bundle.ts b/src/transpilation/bundle.ts index 0c039ad4f..ed2909c03 100644 --- a/src/transpilation/bundle.ts +++ b/src/transpilation/bundle.ts @@ -1,7 +1,7 @@ import * as path from "path"; import { SourceNode } from "source-map"; import * as ts from "typescript"; -import { CompilerOptions } from "../CompilerOptions"; +import { CompilerOptions, LuaTarget } from "../CompilerOptions"; import { escapeString, tstlHeader } from "../LuaPrinter"; import { cast, formatPathToLuaPath, isNonNull, trimExtension } from "../utils"; import { couldNotFindBundleEntryPoint } from "./diagnostics"; @@ -12,7 +12,9 @@ const createModulePath = (pathToResolve: string, program: ts.Program) => escapeString(formatPathToLuaPath(trimExtension(getEmitPathRelativeToOutDir(pathToResolve, program)))); // Override `require` to read from ____modules table. -const requireOverride = ` +function requireOverride(options: CompilerOptions) { + const isLua50 = options.luaTarget === LuaTarget.Lua50; + return ` local ____modules = {} local ____moduleCache = {} local ____originalRequire = require @@ -22,7 +24,9 @@ local function require(file, ...) end if ____modules[file] then local module = ____modules[file] - ____moduleCache[file] = { value = (select("#", ...) > 0) and module(...) or module(file) } + ____moduleCache[file] = { value = (${isLua50 ? "table.getn(arg)" : 'select("#", ...)'} > 0) and module(${ + isLua50 ? "unpack(arg)" : "..." + }) or module(file) } return ____moduleCache[file].value else if ____originalRequire then @@ -33,6 +37,7 @@ local function require(file, ...) end end `; +} export const sourceMapTracebackBundlePlaceholder = "{#SourceMapTracebackBundle}"; @@ -99,7 +104,9 @@ export function getBundleResult(program: ts.Program, files: ProcessedFile[]): [t const moduleTable = createModuleTableNode(moduleTableEntries); // return require("") - const entryPoint = `return require(${createModulePath(entryModule, program)}, ...)\n`; + const entryPoint = `return require(${createModulePath(entryModule, program)}, ${ + options.luaTarget === LuaTarget.Lua50 ? "unpack(arg == nil and {} or arg)" : "..." + })\n`; const footers: string[] = []; if (options.sourceMapTraceback) { @@ -108,7 +115,7 @@ export function getBundleResult(program: ts.Program, files: ProcessedFile[]): [t footers.push(`${sourceMapTracebackBundlePlaceholder}\n`); } - const sourceChunks = [requireOverride, moduleTable, ...footers, entryPoint]; + const sourceChunks = [requireOverride(options), moduleTable, ...footers, entryPoint]; if (!options.noHeader) { sourceChunks.unshift(tstlHeader); From 56727c9b1ee453c975673a1d0ae02027394c33d0 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 19 May 2022 06:06:18 +0000 Subject: [PATCH 20/69] fix(5.0): optimized single element array push for 5.0 --- src/transformation/builtins/array.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/transformation/builtins/array.ts b/src/transformation/builtins/array.ts index 8589e05e7..3fb3f5cf2 100644 --- a/src/transformation/builtins/array.ts +++ b/src/transformation/builtins/array.ts @@ -30,6 +30,8 @@ export function transformArrayConstructorCall( } } +const lua50TableLength = lua.createTableIndexExpression(lua.createIdentifier("table"), lua.createStringLiteral("getn")); + /** * Optimized single element Array.push * @@ -48,7 +50,9 @@ function transformSingleElementArrayPush( // #array + 1 let lengthExpression: lua.Expression = lua.createBinaryExpression( - lua.createUnaryExpression(arrayIdentifier, lua.SyntaxKind.LengthOperator), + context.luaTarget === LuaTarget.Lua50 + ? lua.createCallExpression(lua50TableLength, [arrayIdentifier]) + : lua.createUnaryExpression(arrayIdentifier, lua.SyntaxKind.LengthOperator), lua.createNumericLiteral(1), lua.SyntaxKind.AdditionOperator ); @@ -182,15 +186,9 @@ export function transformArrayProperty( switch (node.name.text) { case "length": const expression = context.transformExpression(node.expression); - if (context.luaTarget === LuaTarget.Lua50) { - const tableGetn = lua.createTableIndexExpression( - lua.createIdentifier("table"), - lua.createStringLiteral("getn") - ); - return lua.createCallExpression(tableGetn, [expression], node); - } else { - return lua.createUnaryExpression(expression, lua.SyntaxKind.LengthOperator, node); - } + return context.luaTarget === LuaTarget.Lua50 + ? lua.createCallExpression(lua50TableLength, [expression], node) + : lua.createUnaryExpression(expression, lua.SyntaxKind.LengthOperator, node); default: return undefined; } From 110e3793776a603a80da0336722d7e269be9768a Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Tue, 24 May 2022 05:44:19 +0000 Subject: [PATCH 21/69] fix(5.0): fix constructor w/ instance fields for 5.0 --- src/transformation/visitors/class/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index 80b5f7536..775a3dfc7 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -25,6 +25,7 @@ import { import { createMethodDecoratingExpression, transformMethodDeclaration } from "./members/method"; import { getExtendedNode, getExtendedType, isStaticNode } from "./utils"; import { createClassSetup } from "./setup"; +import { LuaTarget } from "../../../CompilerOptions"; export const transformClassDeclaration: FunctionVisitor = (declaration, context) => { // If declaration is a default export, transform to export variable assignment instead @@ -138,16 +139,20 @@ function transformClassLikeDeclaration( } else if (instanceFields.length > 0) { // Generate a constructor if none was defined in a class with instance fields that need initialization // localClassName.prototype.____constructor = function(self, ...) - // baseClassName.prototype.____constructor(self, ...) + // baseClassName.prototype.____constructor(self, ...) // or unpack(arg) for Lua 5.0 // ... const constructorBody = transformClassInstanceFields(context, instanceFields); + const argsExpression = + context.luaTarget === LuaTarget.Lua50 + ? lua.createCallExpression(lua.createIdentifier("unpack"), [lua.createArgLiteral()]) + : lua.createDotsLiteral(); const superCall = lua.createExpressionStatement( lua.createCallExpression( lua.createTableIndexExpression( context.transformExpression(ts.factory.createSuper()), lua.createStringLiteral("____constructor") ), - [createSelfIdentifier(), lua.createDotsLiteral()] + [createSelfIdentifier(), argsExpression] ) ); constructorBody.unshift(superCall); From bc89dce2309b1d4dad4823c20f505bc4026b03e0 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 7 May 2022 05:47:41 +0000 Subject: [PATCH 22/69] fix(lualib): count varargs correctly with lua 5.0 --- src/LuaLib.ts | 1 + src/lualib/ArrayReduce.ts | 4 +++- src/lualib/ArrayReduceRight.ts | 4 +++- src/lualib/ArraySplice.ts | 4 +++- src/lualib/CountVarargs.ts | 7 +++++++ src/lualib/Generator.ts | 3 ++- src/lualib/SparseArrayNew.ts | 4 ++-- src/lualib/SparseArrayPush.ts | 3 ++- 8 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 src/lualib/CountVarargs.ts diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 88dd33040..14b5dba72 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -34,6 +34,7 @@ export enum LuaLibFeature { Class = "Class", ClassExtends = "ClassExtends", CloneDescriptor = "CloneDescriptor", + CountVarargs = "CountVarargs", Decorate = "Decorate", DecorateParam = "DecorateParam", Delete = "Delete", diff --git a/src/lualib/ArrayReduce.ts b/src/lualib/ArrayReduce.ts index eb26a9731..d21d97403 100644 --- a/src/lualib/ArrayReduce.ts +++ b/src/lualib/ArrayReduce.ts @@ -1,3 +1,5 @@ +import { __TS__CountVarargs } from "./CountVarargs"; + // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-array.prototype.reduce export function __TS__ArrayReduce( this: TElement[], @@ -10,7 +12,7 @@ export function __TS__ArrayReduce( let accumulator: TAccumulator = undefined; // Check if initial value is present in function call - if (select("#", ...initial) !== 0) { + if (__TS__CountVarargs(...initial) !== 0) { [accumulator] = [...initial]; } else if (len > 0) { accumulator = this[0] as unknown as TAccumulator; diff --git a/src/lualib/ArrayReduceRight.ts b/src/lualib/ArrayReduceRight.ts index e32e87e79..13bfe17d9 100644 --- a/src/lualib/ArrayReduceRight.ts +++ b/src/lualib/ArrayReduceRight.ts @@ -1,3 +1,5 @@ +import { __TS__CountVarargs } from "./CountVarargs"; + // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-array.prototype.reduce export function __TS__ArrayReduceRight( this: TElement[], @@ -10,7 +12,7 @@ export function __TS__ArrayReduceRight( let accumulator: TAccumulator = undefined; // Check if initial value is present in function call - if (select("#", ...initial) !== 0) { + if (__TS__CountVarargs(...initial) !== 0) { [accumulator] = [...initial]; } else if (len > 0) { accumulator = this[k] as unknown as TAccumulator; diff --git a/src/lualib/ArraySplice.ts b/src/lualib/ArraySplice.ts index dc69e16fb..1d14cbda2 100644 --- a/src/lualib/ArraySplice.ts +++ b/src/lualib/ArraySplice.ts @@ -1,8 +1,10 @@ +import { __TS__CountVarargs } from "./CountVarargs"; + // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-array.prototype.splice export function __TS__ArraySplice(this: T[], ...args: any[]): T[] { const len = this.length; - const actualArgumentCount = select("#", ...args); + const actualArgumentCount = __TS__CountVarargs(...args); let start = args[0] as number; const deleteCount = args[1] as number; diff --git a/src/lualib/CountVarargs.ts b/src/lualib/CountVarargs.ts new file mode 100644 index 000000000..dce362c55 --- /dev/null +++ b/src/lualib/CountVarargs.ts @@ -0,0 +1,7 @@ +/** @noSelfInFile */ + +export function __TS__CountVarargs(...args: T[]): number { + // In Lua 5.0, the arg table includes trailing nils. In all other + // versions, we must use select() to count trailing nils. + return _VERSION === "Lua 5.0" ? args.length : select("#", ...args); +} diff --git a/src/lualib/Generator.ts b/src/lualib/Generator.ts index 0122602a2..d64bf781a 100644 --- a/src/lualib/Generator.ts +++ b/src/lualib/Generator.ts @@ -1,3 +1,4 @@ +import { __TS__CountVarargs } from "./CountVarargs"; import { GeneratorIterator } from "./GeneratorIterator"; function generatorIterator(this: GeneratorIterator) { @@ -16,7 +17,7 @@ function generatorNext(this: GeneratorIterator, ...args: any[]) { export function __TS__Generator(this: void, fn: (this: void, ...args: any[]) => any) { return function (this: void, ...args: any[]): GeneratorIterator { - const argsLength = select("#", ...args); + const argsLength = __TS__CountVarargs(...args); return { // Using explicit this there, since we don't pass arguments after the first nil and context is likely to be nil ____coroutine: coroutine.create(() => fn(...(unpack ?? table.unpack)(args, 1, argsLength))), diff --git a/src/lualib/SparseArrayNew.ts b/src/lualib/SparseArrayNew.ts index 07bfae13e..c6877b9c2 100644 --- a/src/lualib/SparseArrayNew.ts +++ b/src/lualib/SparseArrayNew.ts @@ -1,9 +1,9 @@ +import { __TS__CountVarargs } from "./CountVarargs"; import { __TS__SparseArray } from "./SparseArray"; export function __TS__SparseArrayNew(this: void, ...args: T[]): __TS__SparseArray { const sparseArray = [...args] as __TS__SparseArray; - // select("#", ...) counts the number of args passed, including nils. // Note that we're depending on vararg optimization to occur here. - sparseArray.sparseLength = select("#", ...args); + sparseArray.sparseLength = __TS__CountVarargs(...args); return sparseArray; } diff --git a/src/lualib/SparseArrayPush.ts b/src/lualib/SparseArrayPush.ts index 8a089a192..253d6e223 100644 --- a/src/lualib/SparseArrayPush.ts +++ b/src/lualib/SparseArrayPush.ts @@ -1,7 +1,8 @@ +import { __TS__CountVarargs } from "./CountVarargs"; import { __TS__SparseArray } from "./SparseArray"; export function __TS__SparseArrayPush(this: void, sparseArray: __TS__SparseArray, ...args: T[]): void { - const argsLen = select("#", ...args); + const argsLen = __TS__CountVarargs(...args); const listLen = sparseArray.sparseLength; for (const i of $range(1, argsLen)) { sparseArray[listLen + i - 1] = args[i - 1]; From 36324fb301a2b545b4e1b69a856abcbfd742e4d6 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 7 May 2022 05:48:04 +0000 Subject: [PATCH 23/69] fix(lualib): tracebacks don't support threads with lua 5.0 --- src/lualib/SourceMapTraceBack.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lualib/SourceMapTraceBack.ts b/src/lualib/SourceMapTraceBack.ts index fe1714f1f..2d07d0536 100644 --- a/src/lualib/SourceMapTraceBack.ts +++ b/src/lualib/SourceMapTraceBack.ts @@ -18,6 +18,8 @@ export function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap let trace: string; if (thread === undefined && message === undefined && level === undefined) { trace = originalTraceback(); + } else if (_VERSION === "Lua 5.0") { + trace = originalTraceback(message, level); } else { trace = originalTraceback(thread, message, level); } From 18d3c7a0bd955176acd68b7c9161b083dd9c2167 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 7 May 2022 05:56:26 +0000 Subject: [PATCH 24/69] fix(lualib): work around missing math.modf() in lua 5.0 --- src/lualib/NumberToString.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lualib/NumberToString.ts b/src/lualib/NumberToString.ts index 1e1a1ad03..c7de210e7 100644 --- a/src/lualib/NumberToString.ts +++ b/src/lualib/NumberToString.ts @@ -1,5 +1,14 @@ const radixChars = "0123456789abcdefghijklmnopqrstuvwxyz"; +function modf(this: void, x: number): LuaMultiReturn<[number, number]> { + if (_VERSION === "Lua 5.0") { + const integral = x > 0 ? Math.floor(x) : Math.ceil(x); + return $multi(integral, x - integral); + } else { + return math.modf(x); + } +} + // https://www.ecma-international.org/ecma-262/10.0/index.html#sec-number.prototype.tostring export function __TS__NumberToString(this: number, radix?: number): string { if (radix === undefined || radix === 10 || this === Infinity || this === -Infinity || this !== this) { @@ -11,7 +20,7 @@ export function __TS__NumberToString(this: number, radix?: number): string { throw "toString() radix argument must be between 2 and 36"; } - let [integer, fraction] = math.modf(Math.abs(this)); + let [integer, fraction] = modf(Math.abs(this)); let result = ""; if (radix === 8) { From f57f75bfa1b19a9c1aab8303c3a9151c8273c3f3 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sun, 8 May 2022 06:47:14 +0000 Subject: [PATCH 25/69] fix(lualib): work around missing string.match() in lua 5.0 --- src/LuaLib.ts | 1 + src/lualib/Match.ts | 16 ++++++++++++++++ src/lualib/ParseFloat.ts | 6 ++++-- src/lualib/ParseInt.ts | 8 +++++--- src/lualib/SourceMapTraceBack.ts | 4 +++- 5 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 src/lualib/Match.ts diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 14b5dba72..f3ac29121 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -46,6 +46,7 @@ export enum LuaLibFeature { InstanceOfObject = "InstanceOfObject", Iterator = "Iterator", Map = "Map", + Match = "Match", MathAtan2 = "MathAtan2", MathSign = "MathSign", Modulo = "Modulo", diff --git a/src/lualib/Match.ts b/src/lualib/Match.ts new file mode 100644 index 000000000..d2c6a3e3f --- /dev/null +++ b/src/lualib/Match.ts @@ -0,0 +1,16 @@ +/** @noSelfInFile */ + +export function __TS__Match(s: string, pattern: string, init?: number): LuaMultiReturn { + if (_VERSION === "Lua 5.0") { + const [start, end, ...captures] = string.find(s, pattern, init); + if (start === undefined || end === undefined) { + return $multi(); + } else if (captures.length <= 0) { + return $multi(s.slice(start - 1, end)); + } else { + return $multi(...(captures as string[])); + } + } else { + return string.match(s, pattern, init); + } +} diff --git a/src/lualib/ParseFloat.ts b/src/lualib/ParseFloat.ts index 94ca97429..e182370e2 100644 --- a/src/lualib/ParseFloat.ts +++ b/src/lualib/ParseFloat.ts @@ -1,11 +1,13 @@ +import { __TS__Match } from "./Match"; + export function __TS__ParseFloat(this: void, numberString: string): number { // Check if string is infinity - const [infinityMatch] = string.match(numberString, "^%s*(-?Infinity)"); + const [infinityMatch] = __TS__Match(numberString, "^%s*(-?Infinity)"); if (infinityMatch) { // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with return infinityMatch[0] === "-" ? -Infinity : Infinity; } - const number = tonumber(string.match(numberString, "^%s*(-?%d+%.?%d*)")[0]); + const number = tonumber(__TS__Match(numberString, "^%s*(-?%d+%.?%d*)")[0]); return number ?? NaN; } diff --git a/src/lualib/ParseInt.ts b/src/lualib/ParseInt.ts index 208d84e44..2e1081a86 100644 --- a/src/lualib/ParseInt.ts +++ b/src/lualib/ParseInt.ts @@ -1,13 +1,15 @@ +import { __TS__Match } from "./Match"; + const parseIntBasePattern = "0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTvVwWxXyYzZ"; export function __TS__ParseInt(this: void, numberString: string, base?: number): number { // Check which base to use if none specified if (base === undefined) { base = 10; - const [hexMatch] = string.match(numberString, "^%s*-?0[xX]"); + const [hexMatch] = __TS__Match(numberString, "^%s*-?0[xX]"); if (hexMatch) { base = 16; - numberString = string.match(hexMatch, "-")[0] + numberString = __TS__Match(hexMatch, "-")[0] ? "-" + numberString.substr(hexMatch.length) : numberString.substr(hexMatch.length); } @@ -24,7 +26,7 @@ export function __TS__ParseInt(this: void, numberString: string, base?: number): const pattern = `^%s*(-?[${allowedDigits}]*)`; // Try to parse with Lua tonumber - const number = tonumber(string.match(numberString, pattern)[0], base); + const number = tonumber(__TS__Match(numberString, pattern)[0], base); if (number === undefined) { return NaN; diff --git a/src/lualib/SourceMapTraceBack.ts b/src/lualib/SourceMapTraceBack.ts index 2d07d0536..36b1e3b5f 100644 --- a/src/lualib/SourceMapTraceBack.ts +++ b/src/lualib/SourceMapTraceBack.ts @@ -1,6 +1,8 @@ // TODO: In the future, change this to __TS__RegisterFileInfo and provide tstl interface to // get some metadata about transpilation. +import { __TS__Match } from "./Match"; + interface SourceMap { [line: number]: number | { line: number; file: string }; } @@ -49,7 +51,7 @@ export function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap const stringReplacer = (file: string, line: string) => { const fileSourceMap: SourceMap = globalThis.__TS__sourcemap[file]; if (fileSourceMap && fileSourceMap[line]) { - const chunkName = string.match(file, '%[string "([^"]+)"%]')[0]; + const chunkName = __TS__Match(file, '%[string "([^"]+)"%]')[0]; const [sourceName] = string.gsub(chunkName, ".lua$", ".ts"); const data = fileSourceMap[line]; if (typeof data === "number") { From 3496d6736343e0516a40bf9f61f1e215ecf36aed Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sun, 8 May 2022 07:08:07 +0000 Subject: [PATCH 26/69] fix(lualib): detect lua 5.0 minor versions correctly --- src/LuaLib.ts | 1 + src/lualib/CountVarargs.ts | 4 +++- src/lualib/IsLua50.ts | 5 +++++ src/lualib/Match.ts | 4 +++- src/lualib/NumberToString.ts | 4 +++- src/lualib/SourceMapTraceBack.ts | 3 ++- 6 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 src/lualib/IsLua50.ts diff --git a/src/LuaLib.ts b/src/LuaLib.ts index f3ac29121..c2e754f11 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -44,6 +44,7 @@ export enum LuaLibFeature { Generator = "Generator", InstanceOf = "InstanceOf", InstanceOfObject = "InstanceOfObject", + IsLua50 = "IsLua50", Iterator = "Iterator", Map = "Map", Match = "Match", diff --git a/src/lualib/CountVarargs.ts b/src/lualib/CountVarargs.ts index dce362c55..971f89ceb 100644 --- a/src/lualib/CountVarargs.ts +++ b/src/lualib/CountVarargs.ts @@ -1,7 +1,9 @@ /** @noSelfInFile */ +import { __TS__IsLua50 } from "./IsLua50"; + export function __TS__CountVarargs(...args: T[]): number { // In Lua 5.0, the arg table includes trailing nils. In all other // versions, we must use select() to count trailing nils. - return _VERSION === "Lua 5.0" ? args.length : select("#", ...args); + return __TS__IsLua50() ? args.length : select("#", ...args); } diff --git a/src/lualib/IsLua50.ts b/src/lualib/IsLua50.ts new file mode 100644 index 000000000..d6f9d78b2 --- /dev/null +++ b/src/lualib/IsLua50.ts @@ -0,0 +1,5 @@ +/** @noSelfInFile */ + +export function __TS__IsLua50(): boolean { + return _VERSION === "Lua 5.0" || _VERSION === "Lua 5.0.1" || _VERSION === "Lua 5.0.2" || _VERSION === "Lua 5.0.3"; +} diff --git a/src/lualib/Match.ts b/src/lualib/Match.ts index d2c6a3e3f..beb6be016 100644 --- a/src/lualib/Match.ts +++ b/src/lualib/Match.ts @@ -1,7 +1,9 @@ /** @noSelfInFile */ +import { __TS__IsLua50 } from "./IsLua50"; + export function __TS__Match(s: string, pattern: string, init?: number): LuaMultiReturn { - if (_VERSION === "Lua 5.0") { + if (__TS__IsLua50()) { const [start, end, ...captures] = string.find(s, pattern, init); if (start === undefined || end === undefined) { return $multi(); diff --git a/src/lualib/NumberToString.ts b/src/lualib/NumberToString.ts index c7de210e7..f58c16027 100644 --- a/src/lualib/NumberToString.ts +++ b/src/lualib/NumberToString.ts @@ -1,7 +1,9 @@ +import { __TS__IsLua50 } from "./IsLua50"; + const radixChars = "0123456789abcdefghijklmnopqrstuvwxyz"; function modf(this: void, x: number): LuaMultiReturn<[number, number]> { - if (_VERSION === "Lua 5.0") { + if (__TS__IsLua50()) { const integral = x > 0 ? Math.floor(x) : Math.ceil(x); return $multi(integral, x - integral); } else { diff --git a/src/lualib/SourceMapTraceBack.ts b/src/lualib/SourceMapTraceBack.ts index 36b1e3b5f..6dbfa2835 100644 --- a/src/lualib/SourceMapTraceBack.ts +++ b/src/lualib/SourceMapTraceBack.ts @@ -1,6 +1,7 @@ // TODO: In the future, change this to __TS__RegisterFileInfo and provide tstl interface to // get some metadata about transpilation. +import { __TS__IsLua50 } from "./IsLua50"; import { __TS__Match } from "./Match"; interface SourceMap { @@ -20,7 +21,7 @@ export function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap let trace: string; if (thread === undefined && message === undefined && level === undefined) { trace = originalTraceback(); - } else if (_VERSION === "Lua 5.0") { + } else if (__TS__IsLua50()) { trace = originalTraceback(message, level); } else { trace = originalTraceback(thread, message, level); From fe45a47e027fa560a2b610b38e4183ef1e91ee37 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sun, 8 May 2022 19:49:16 +0000 Subject: [PATCH 27/69] refactor(lualib): lua 5.0 check can be a constant instead of a function --- src/lualib/CountVarargs.ts | 2 +- src/lualib/IsLua50.ts | 5 ++--- src/lualib/Match.ts | 2 +- src/lualib/NumberToString.ts | 2 +- src/lualib/SourceMapTraceBack.ts | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lualib/CountVarargs.ts b/src/lualib/CountVarargs.ts index 971f89ceb..a97534aa4 100644 --- a/src/lualib/CountVarargs.ts +++ b/src/lualib/CountVarargs.ts @@ -5,5 +5,5 @@ import { __TS__IsLua50 } from "./IsLua50"; export function __TS__CountVarargs(...args: T[]): number { // In Lua 5.0, the arg table includes trailing nils. In all other // versions, we must use select() to count trailing nils. - return __TS__IsLua50() ? args.length : select("#", ...args); + return __TS__IsLua50 ? args.length : select("#", ...args); } diff --git a/src/lualib/IsLua50.ts b/src/lualib/IsLua50.ts index d6f9d78b2..fd6e8196a 100644 --- a/src/lualib/IsLua50.ts +++ b/src/lualib/IsLua50.ts @@ -1,5 +1,4 @@ /** @noSelfInFile */ -export function __TS__IsLua50(): boolean { - return _VERSION === "Lua 5.0" || _VERSION === "Lua 5.0.1" || _VERSION === "Lua 5.0.2" || _VERSION === "Lua 5.0.3"; -} +export const __TS__IsLua50 = + _VERSION === "Lua 5.0" || _VERSION === "Lua 5.0.1" || _VERSION === "Lua 5.0.2" || _VERSION === "Lua 5.0.3"; diff --git a/src/lualib/Match.ts b/src/lualib/Match.ts index beb6be016..d800097a7 100644 --- a/src/lualib/Match.ts +++ b/src/lualib/Match.ts @@ -3,7 +3,7 @@ import { __TS__IsLua50 } from "./IsLua50"; export function __TS__Match(s: string, pattern: string, init?: number): LuaMultiReturn { - if (__TS__IsLua50()) { + if (__TS__IsLua50) { const [start, end, ...captures] = string.find(s, pattern, init); if (start === undefined || end === undefined) { return $multi(); diff --git a/src/lualib/NumberToString.ts b/src/lualib/NumberToString.ts index f58c16027..349771c52 100644 --- a/src/lualib/NumberToString.ts +++ b/src/lualib/NumberToString.ts @@ -3,7 +3,7 @@ import { __TS__IsLua50 } from "./IsLua50"; const radixChars = "0123456789abcdefghijklmnopqrstuvwxyz"; function modf(this: void, x: number): LuaMultiReturn<[number, number]> { - if (__TS__IsLua50()) { + if (__TS__IsLua50) { const integral = x > 0 ? Math.floor(x) : Math.ceil(x); return $multi(integral, x - integral); } else { diff --git a/src/lualib/SourceMapTraceBack.ts b/src/lualib/SourceMapTraceBack.ts index 6dbfa2835..9bb741e20 100644 --- a/src/lualib/SourceMapTraceBack.ts +++ b/src/lualib/SourceMapTraceBack.ts @@ -21,7 +21,7 @@ export function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap let trace: string; if (thread === undefined && message === undefined && level === undefined) { trace = originalTraceback(); - } else if (__TS__IsLua50()) { + } else if (__TS__IsLua50) { trace = originalTraceback(message, level); } else { trace = originalTraceback(thread, message, level); From da5fcb5102e072c18d709b3baebba408dfb2f405 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Tue, 16 Aug 2022 17:34:03 +0000 Subject: [PATCH 28/69] fix(5.0): get json test library to load --- test/json.5.0.lua | 145 ++++++++++++++++++++++++++++++++++++++++++++++ test/util.ts | 39 +++++++++---- 2 files changed, 173 insertions(+), 11 deletions(-) create mode 100644 test/json.5.0.lua diff --git a/test/json.5.0.lua b/test/json.5.0.lua new file mode 100644 index 000000000..56eddb364 --- /dev/null +++ b/test/json.5.0.lua @@ -0,0 +1,145 @@ +-- +-- json.lua +-- +-- Copyright (c) 2015 rxi +-- +-- This library is free software; you can redistribute it and/or modify it +-- under the terms of the MIT license. See LICENSE for details. +-- +-- +-- LICENSE CONTENTS: +-- +-- Copyright (c) 2015 rxi +-- +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. + +local json = { _version = "0.1.0" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\\\", + [ "\"" ] = "\\\"", + [ "\b" ] = "\\b", + [ "\f" ] = "\\f", + [ "\n" ] = "\\n", + [ "\r" ] = "\\r", + [ "\t" ] = "\\t", +} + +local escape_char_map_inv = { [ "\\/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return escape_char_map[c] or string.format("\\u%04x", c:byte()) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if val[1] ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= table.getn(val) then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + if val ~= val then + return "NaN" + elseif val > 1.79e308 then + return "Infinity" + elseif val < -1.79e308 then + return "-Infinity" + else + return string.format("%.17g", val) + end +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + +return {stringify = function(val) return ( encode(val) ) end} diff --git a/test/util.ts b/test/util.ts index a14847ac3..39fd63c60 100644 --- a/test/util.ts +++ b/test/util.ts @@ -12,13 +12,21 @@ import { createEmitOutputCollector } from "../src/transpilation/output-collector import { EmitHost, getEmitOutDir, transpileProject } from "../src"; import { formatPathToLuaPath, normalizeSlashes } from "../src/utils"; -const jsonLib = fs.readFileSync(path.join(__dirname, "json.lua"), "utf8"); const luaLib = fs.readFileSync(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"), "utf8"); +function jsonLib(target: tstl.LuaTarget): string { + const fileName = target === tstl.LuaTarget.Lua50 ? "json.5.0.lua" : "json.lua"; + return fs.readFileSync(path.join(__dirname, fileName), "utf8"); +} + // Using `test` directly makes eslint-plugin-jest consider this file as a test const defineTest = test; function getLuaBindingsForVersion(target: tstl.LuaTarget): { lauxlib: LauxLib; lua: Lua; lualib: LuaLib } { + if (target === tstl.LuaTarget.Lua50) { + const { lauxlib, lua, lualib } = require("lua-wasm-bindings/dist/lua.50"); + return { lauxlib, lua, lualib }; + } if (target === tstl.LuaTarget.Lua51) { const { lauxlib, lua, lualib } = require("lua-wasm-bindings/dist/lua.51"); return { lauxlib, lua, lualib }; @@ -403,20 +411,21 @@ export abstract class TestBuilder { // Main file const mainFile = this.getMainLuaCodeChunk(); - const { lauxlib, lua, lualib } = getLuaBindingsForVersion(this.options.luaTarget ?? tstl.LuaTarget.Lua54); + const luaTarget = this.options.luaTarget ?? tstl.LuaTarget.Lua54; + const { lauxlib, lua, lualib } = getLuaBindingsForVersion(luaTarget); const L = lauxlib.luaL_newstate(); lualib.luaL_openlibs(L); // Load modules // Json - this.packagePreloadLuaFile(L, lua, lauxlib, "json", jsonLib); + this.injectLuaFile(L, lua, lauxlib, "json", jsonLib(luaTarget)); // Lua lib if ( this.options.luaLibImport === tstl.LuaLibImportKind.Require || mainFile.includes('require("lualib_bundle")') ) { - this.packagePreloadLuaFile(L, lua, lauxlib, "lualib_bundle", luaLib); + this.injectLuaFile(L, lua, lauxlib, "lualib_bundle", luaLib); } // Load all transpiled files into Lua's package cache @@ -424,7 +433,7 @@ export abstract class TestBuilder { for (const transpiledFile of transpiledFiles) { if (transpiledFile.lua) { const filePath = path.relative(getEmitOutDir(this.getProgram()), transpiledFile.outPath); - this.packagePreloadLuaFile(L, lua, lauxlib, filePath, transpiledFile.lua); + this.injectLuaFile(L, lua, lauxlib, filePath, transpiledFile.lua); } } @@ -455,12 +464,20 @@ end)());`; } } - private packagePreloadLuaFile(state: LuaState, lua: Lua, lauxlib: LauxLib, fileName: string, fileContent: string) { - // Adding source Lua to the package.preload cache will allow require to find it - lua.lua_getglobal(state, "package"); - lua.lua_getfield(state, -1, "preload"); - lauxlib.luaL_loadstring(state, fileContent); - lua.lua_setfield(state, -2, formatPathToLuaPath(fileName.replace(".lua", ""))); + private injectLuaFile(state: LuaState, lua: Lua, lauxlib: LauxLib, fileName: string, fileContent: string) { + const modName = formatPathToLuaPath(fileName.replace(".lua", "")); + if (this.options.luaTarget === tstl.LuaTarget.Lua50) { + // Adding source Lua to the _LOADED cache will allow require to find it + lua.lua_getglobal(state, "_LOADED"); + lauxlib.luaL_dostring(state, fileContent); + lua.lua_setfield(state, -2, modName); + } else { + // Adding source Lua to the package.preload cache will allow require to find it + lua.lua_getglobal(state, "package"); + lua.lua_getfield(state, -1, "preload"); + lauxlib.luaL_loadstring(state, fileContent); + lua.lua_setfield(state, -2, modName); + } } private executeJs(): any { From b410d084c06242187dc67785a924e02eb27bff52 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Wed, 17 Aug 2022 19:20:58 +0000 Subject: [PATCH 29/69] style: rename 5.0 json file --- test/{json.5.0.lua => json.50.lua} | 0 test/util.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename test/{json.5.0.lua => json.50.lua} (100%) diff --git a/test/json.5.0.lua b/test/json.50.lua similarity index 100% rename from test/json.5.0.lua rename to test/json.50.lua diff --git a/test/util.ts b/test/util.ts index 39fd63c60..7bb9b5213 100644 --- a/test/util.ts +++ b/test/util.ts @@ -15,7 +15,7 @@ import { formatPathToLuaPath, normalizeSlashes } from "../src/utils"; const luaLib = fs.readFileSync(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"), "utf8"); function jsonLib(target: tstl.LuaTarget): string { - const fileName = target === tstl.LuaTarget.Lua50 ? "json.5.0.lua" : "json.lua"; + const fileName = target === tstl.LuaTarget.Lua50 ? "json.50.lua" : "json.lua"; return fs.readFileSync(path.join(__dirname, fileName), "utf8"); } From 295605837a25f36bf6dbbac33aed8d4a7c341823 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Wed, 17 Aug 2022 19:21:28 +0000 Subject: [PATCH 30/69] style: use a better definition of infinity --- test/json.50.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/json.50.lua b/test/json.50.lua index 56eddb364..bf6f06816 100644 --- a/test/json.50.lua +++ b/test/json.50.lua @@ -114,9 +114,9 @@ end local function encode_number(val) if val ~= val then return "NaN" - elseif val > 1.79e308 then + elseif val >= 1 / 0 then return "Infinity" - elseif val < -1.79e308 then + elseif val <= 0 / 0 then return "-Infinity" else return string.format("%.17g", val) From f0976dfda7a33ef33bb7fa11de743e099ae0f2cb Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Wed, 17 Aug 2022 19:23:09 +0000 Subject: [PATCH 31/69] feat(5.0): don't transform infinity to math.huge, which doesn't exist in 5.0 --- src/transformation/builtins/index.ts | 13 ++++++++++--- src/transformation/visitors/literal.ts | 15 +++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/transformation/builtins/index.ts b/src/transformation/builtins/index.ts index b5a92095b..7403425f0 100644 --- a/src/transformation/builtins/index.ts +++ b/src/transformation/builtins/index.ts @@ -23,6 +23,7 @@ import { transformPromiseConstructorCall } from "./promise"; import { transformStringConstructorCall, transformStringProperty, transformStringPrototypeCall } from "./string"; import { transformSymbolConstructorCall } from "./symbol"; import { unsupportedBuiltinOptionalCall } from "../utils/diagnostics"; +import { LuaTarget } from "../../CompilerOptions"; export function transformBuiltinPropertyAccessExpression( context: TransformationContext, @@ -161,9 +162,15 @@ export function transformBuiltinIdentifierExpression( return createNaN(node); case "Infinity": - const math = lua.createIdentifier("math"); - const huge = lua.createStringLiteral("huge"); - return lua.createTableIndexExpression(math, huge, node); + if (context.luaTarget === LuaTarget.Lua50) { + const one = lua.createNumericLiteral(1); + const zero = lua.createNumericLiteral(0); + return lua.createBinaryExpression(one, zero, lua.SyntaxKind.DivisionOperator); + } else { + const math = lua.createIdentifier("math"); + const huge = lua.createStringLiteral("huge"); + return lua.createTableIndexExpression(math, huge, node); + } case "globalThis": return lua.createIdentifier("_G", node, getIdentifierSymbolId(context, node, symbol), "globalThis"); diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index 838b8d5c2..1d91c89d9 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -9,6 +9,7 @@ import { isArrayType } from "../utils/typescript"; import { transformFunctionLikeDeclaration } from "./function"; import { moveToPrecedingTemp, transformExpressionList } from "./expression-list"; import { transformIdentifierWithSymbol } from "./identifier"; +import { LuaTarget } from "../../CompilerOptions"; // TODO: Move to object-literal.ts? export function transformPropertyName(context: TransformationContext, node: ts.PropertyName): lua.Expression { @@ -31,11 +32,17 @@ export function createShorthandIdentifier( return transformIdentifierWithSymbol(context, propertyIdentifier, valueSymbol); } -const transformNumericLiteralExpression: FunctionVisitor = expression => { +const transformNumericLiteralExpression: FunctionVisitor = (expression, context) => { if (expression.text === "Infinity") { - const math = lua.createIdentifier("math"); - const huge = lua.createStringLiteral("huge"); - return lua.createTableIndexExpression(math, huge, expression); + if (context.luaTarget === LuaTarget.Lua50) { + const one = lua.createNumericLiteral(1); + const zero = lua.createNumericLiteral(0); + return lua.createBinaryExpression(one, zero, lua.SyntaxKind.DivisionOperator); + } else { + const math = lua.createIdentifier("math"); + const huge = lua.createStringLiteral("huge"); + return lua.createTableIndexExpression(math, huge, expression); + } } return lua.createNumericLiteral(Number(expression.text), expression); From cc427a2dae72a653ce9b5b444399ddb49fa3725a Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 18 Aug 2022 00:48:12 +0000 Subject: [PATCH 32/69] chore: bump lua-types version --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 767d8d74b..0b12938aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4803,9 +4803,9 @@ } }, "node_modules/lua-types": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/lua-types/-/lua-types-2.11.0.tgz", - "integrity": "sha512-J/8qHrOjkZuPBDhnuAg0DWoaJEImqXNzgEnUigCTVrSVCcchM5Y5zpcI3aWAZPhuZlb0QKC/f3nc7VGmZmBg7w==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/lua-types/-/lua-types-2.12.0.tgz", + "integrity": "sha512-s96z1Zcz3KRXfWyNFM3KP2zpNYTfZwgHo8X/yFXveyS8Dux+cUuyT+zA5tFZbmGh8VViwNs9bFbzNevAR/18uw==", "dev": true, "peerDependencies": { "typescript-to-lua": "^1.0.0" @@ -9974,9 +9974,9 @@ } }, "lua-types": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/lua-types/-/lua-types-2.11.0.tgz", - "integrity": "sha512-J/8qHrOjkZuPBDhnuAg0DWoaJEImqXNzgEnUigCTVrSVCcchM5Y5zpcI3aWAZPhuZlb0QKC/f3nc7VGmZmBg7w==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/lua-types/-/lua-types-2.12.0.tgz", + "integrity": "sha512-s96z1Zcz3KRXfWyNFM3KP2zpNYTfZwgHo8X/yFXveyS8Dux+cUuyT+zA5tFZbmGh8VViwNs9bFbzNevAR/18uw==", "dev": true, "requires": {} }, From a1abb9ee7f879dcfa5c55c94607ddec0971d7186 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 18 Aug 2022 00:56:25 +0000 Subject: [PATCH 33/69] feat: build another copy of lualib for 5.0 --- package.json | 3 ++- src/LuaLib.ts | 37 ++++++++++++++++++++++----------- src/LuaPrinter.ts | 7 ++++--- src/lualib-build/plugin.ts | 3 ++- src/lualib-lua50/tsconfig.json | 21 +++++++++++++++++++ src/transpilation/transpiler.ts | 5 +++-- 6 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 src/lualib-lua50/tsconfig.json diff --git a/package.json b/package.json index 436883362..ead3c73a1 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,14 @@ "dist/**/*.lua", "dist/**/*.ts", "dist/lualib/*.json", + "dist/lualib-lua50/*.json", "language-extensions/**/*.ts" ], "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { "build": "tsc && npm run build-lualib", - "build-lualib": "node dist/tstl.js -p src/lualib/tsconfig.json", + "build-lualib": "node dist/tstl.js -p src/lualib/tsconfig.json && node dist/tstl.js -p src/lualib-lua50/tsconfig.json", "pretest": "npm run lint && npm run check:language-extensions && npm run build-lualib", "test": "jest", "lint": "npm run lint:eslint && npm run lint:prettier", diff --git a/src/LuaLib.ts b/src/LuaLib.ts index c16c7eb9e..e5f3d1baa 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -1,6 +1,7 @@ import * as path from "path"; import { EmitHost } from "./transpilation"; import * as lua from "./LuaAST"; +import { LuaTarget } from "./CompilerOptions"; export enum LuaLibFeature { ArrayConcat = "ArrayConcat", @@ -113,9 +114,10 @@ export type LuaLibModulesInfo = Record; export const luaLibModulesInfoFileName = "lualib_module_info.json"; let luaLibModulesInfo: LuaLibModulesInfo | undefined; -export function getLuaLibModulesInfo(emitHost: EmitHost): LuaLibModulesInfo { +export function getLuaLibModulesInfo(luaTarget: LuaTarget, emitHost: EmitHost): LuaLibModulesInfo { if (luaLibModulesInfo === undefined) { - const lualibPath = path.resolve(__dirname, `../dist/lualib/${luaLibModulesInfoFileName}`); + const lualibDir = `../dist/${luaTarget === LuaTarget.Lua50 ? "lualib-lua50" : "lualib"}`; + const lualibPath = path.resolve(__dirname, `${lualibDir}/${luaLibModulesInfoFileName}`); const result = emitHost.readFile(lualibPath); if (result !== undefined) { luaLibModulesInfo = JSON.parse(result) as LuaLibModulesInfo; @@ -126,8 +128,9 @@ export function getLuaLibModulesInfo(emitHost: EmitHost): LuaLibModulesInfo { return luaLibModulesInfo; } -export function readLuaLibFeature(feature: LuaLibFeature, emitHost: EmitHost): string { - const featurePath = path.resolve(__dirname, `../dist/lualib/${feature}.lua`); +export function readLuaLibFeature(feature: LuaLibFeature, luaTarget: LuaTarget, emitHost: EmitHost): string { + const lualibDir = `../dist/${luaTarget === LuaTarget.Lua50 ? "lualib-lua50" : "lualib"}`; + const featurePath = path.resolve(__dirname, `${lualibDir}/${feature}.lua`); const luaLibFeature = emitHost.readFile(featurePath); if (luaLibFeature === undefined) { throw new Error(`Could not load lualib feature from '${featurePath}'`); @@ -137,8 +140,9 @@ export function readLuaLibFeature(feature: LuaLibFeature, emitHost: EmitHost): s export function resolveRecursiveLualibFeatures( features: Iterable, + luaTarget: LuaTarget, emitHost: EmitHost, - luaLibModulesInfo: LuaLibModulesInfo = getLuaLibModulesInfo(emitHost) + luaLibModulesInfo: LuaLibModulesInfo = getLuaLibModulesInfo(luaTarget, emitHost) ): LuaLibFeature[] { const loadedFeatures = new Set(); const result: LuaLibFeature[] = []; @@ -162,19 +166,27 @@ export function resolveRecursiveLualibFeatures( return result; } -export function loadInlineLualibFeatures(features: Iterable, emitHost: EmitHost): string { +export function loadInlineLualibFeatures( + features: Iterable, + luaTarget: LuaTarget, + emitHost: EmitHost +): string { let result = ""; - for (const feature of resolveRecursiveLualibFeatures(features, emitHost)) { - const luaLibFeature = readLuaLibFeature(feature, emitHost); + for (const feature of resolveRecursiveLualibFeatures(features, luaTarget, emitHost)) { + const luaLibFeature = readLuaLibFeature(feature, luaTarget, emitHost); result += luaLibFeature + "\n"; } return result; } -export function loadImportedLualibFeatures(features: Iterable, emitHost: EmitHost): lua.Statement[] { - const luaLibModuleInfo = getLuaLibModulesInfo(emitHost); +export function loadImportedLualibFeatures( + features: Iterable, + luaTarget: LuaTarget, + emitHost: EmitHost +): lua.Statement[] { + const luaLibModuleInfo = getLuaLibModulesInfo(luaTarget, emitHost); const imports = Array.from(features).flatMap(feature => luaLibModuleInfo[feature].exports); @@ -201,9 +213,10 @@ export function loadImportedLualibFeatures(features: Iterable, em } let luaLibBundleContent: string; -export function getLuaLibBundle(emitHost: EmitHost): string { +export function getLuaLibBundle(luaTarget: LuaTarget, emitHost: EmitHost): string { if (luaLibBundleContent === undefined) { - const lualibPath = path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"); + const lualibDir = `../dist/${luaTarget === LuaTarget.Lua50 ? "lualib-lua50" : "lualib"}`; + const lualibPath = path.resolve(__dirname, `${lualibDir}/lualib_bundle.lua`); const result = emitHost.readFile(lualibPath); if (result !== undefined) { luaLibBundleContent = result; diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 6997ab81f..348b153fc 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -1,7 +1,7 @@ import * as path from "path"; import { Mapping, SourceMapGenerator, SourceNode } from "source-map"; import * as ts from "typescript"; -import { CompilerOptions, isBundleEnabled, LuaLibImportKind } from "./CompilerOptions"; +import { CompilerOptions, isBundleEnabled, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; import * as lua from "./LuaAST"; import { loadInlineLualibFeatures, LuaLibFeature, loadImportedLualibFeatures } from "./LuaLib"; import { isValidLuaIdentifier, shouldAllowUnicode } from "./transformation/utils/safe-names"; @@ -233,14 +233,15 @@ export class LuaPrinter { sourceChunks.push(tstlHeader); } + const luaTarget = this.options.luaTarget ?? LuaTarget.Lua54; const luaLibImport = this.options.luaLibImport ?? LuaLibImportKind.Require; if (luaLibImport === LuaLibImportKind.Require && file.luaLibFeatures.size > 0) { // Import lualib features - sourceChunks = this.printStatementArray(loadImportedLualibFeatures(file.luaLibFeatures, this.emitHost)); + sourceChunks = this.printStatementArray(loadImportedLualibFeatures(file.luaLibFeatures, luaTarget, this.emitHost)); } else if (luaLibImport === LuaLibImportKind.Inline && file.luaLibFeatures.size > 0) { // Inline lualib features sourceChunks.push("-- Lua Library inline imports\n"); - sourceChunks.push(loadInlineLualibFeatures(file.luaLibFeatures, this.emitHost)); + sourceChunks.push(loadInlineLualibFeatures(file.luaLibFeatures, luaTarget, this.emitHost)); sourceChunks.push("-- End of Lua Library inline imports\n"); } diff --git a/src/lualib-build/plugin.ts b/src/lualib-build/plugin.ts index 76b7e5008..3cf1d3d14 100644 --- a/src/lualib-build/plugin.ts +++ b/src/lualib-build/plugin.ts @@ -47,7 +47,8 @@ class LuaLibPlugin implements tstl.Plugin { // Figure out the order required in the bundle by recursively resolving all dependency features const allFeatures = Object.values(LuaLibFeature) as LuaLibFeature[]; - const orderedFeatures = resolveRecursiveLualibFeatures(allFeatures, emitHost, luaLibModuleInfo); + const luaTarget = options.luaTarget ?? tstl.LuaTarget.Lua54; + const orderedFeatures = resolveRecursiveLualibFeatures(allFeatures, luaTarget, emitHost, luaLibModuleInfo); // Concatenate lualib files into bundle with exports table and add lualib_bundle.lua to results let lualibBundle = orderedFeatures.map(f => exportedLualibFeatures.get(LuaLibFeature[f])).join("\n"); diff --git a/src/lualib-lua50/tsconfig.json b/src/lualib-lua50/tsconfig.json new file mode 100644 index 000000000..7cbfe09eb --- /dev/null +++ b/src/lualib-lua50/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "outDir": "../../dist/lualib-lua50", + "target": "esnext", + "lib": ["esnext"], + // Counterintuitively, we do not build with the 5.0 types as they are incompatible with + // branches written for the universal target. For now, the 5.4 types work for both targets. + "types": ["lua-types/5.4"], + "skipLibCheck": true, + + "noUnusedLocals": true, + "noUnusedParameters": true + }, + "tstl": { + "luaLibImport": "none", + "noHeader": true, + "luaTarget": "5.0", + "luaPlugins": [{ "name": "../../dist/lualib-build/plugin.js" }] + }, + "include": ["../lualib", "../../language-extensions/index.d.ts"] +} diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index 216e80aba..2f69f3e21 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -1,6 +1,6 @@ import * as path from "path"; import * as ts from "typescript"; -import { CompilerOptions, isBundleEnabled } from "../CompilerOptions"; +import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; import { normalizeSlashes, trimExtension } from "../utils"; import { getBundleResult } from "./bundle"; @@ -126,7 +126,8 @@ export class Transpiler { // Add lualib bundle to source dir 'virtually', will be moved to correct output dir in emitPlan const fileName = normalizeSlashes(path.resolve(getSourceDir(program), "lualib_bundle.lua")); - resolutionResult.resolvedFiles.unshift({ fileName, code: getLuaLibBundle(this.emitHost) }); + const luaTarget = options.luaTarget ?? LuaTarget.Lua54; + resolutionResult.resolvedFiles.unshift({ fileName, code: getLuaLibBundle(luaTarget, this.emitHost) }); } let emitPlan: EmitFile[]; From 41a219ad7d533862b008fa7392026bda3910966d Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 18 Aug 2022 01:17:04 +0000 Subject: [PATCH 34/69] style: run prettier --- src/LuaPrinter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 348b153fc..044b55207 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -237,7 +237,9 @@ export class LuaPrinter { const luaLibImport = this.options.luaLibImport ?? LuaLibImportKind.Require; if (luaLibImport === LuaLibImportKind.Require && file.luaLibFeatures.size > 0) { // Import lualib features - sourceChunks = this.printStatementArray(loadImportedLualibFeatures(file.luaLibFeatures, luaTarget, this.emitHost)); + sourceChunks = this.printStatementArray( + loadImportedLualibFeatures(file.luaLibFeatures, luaTarget, this.emitHost) + ); } else if (luaLibImport === LuaLibImportKind.Inline && file.luaLibFeatures.size > 0) { // Inline lualib features sourceChunks.push("-- Lua Library inline imports\n"); From 892b2cf364436c0a5c8dad9274b84f8888739de3 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 18 Aug 2022 03:29:42 +0000 Subject: [PATCH 35/69] fix: small issues discovered in testing --- src/transpilation/bundle.ts | 2 +- test/json.50.lua | 10 ++++++---- test/transpile/lualib.spec.ts | 4 ++-- test/util.ts | 9 ++++++--- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/transpilation/bundle.ts b/src/transpilation/bundle.ts index 6e47610a8..d018c1d86 100644 --- a/src/transpilation/bundle.ts +++ b/src/transpilation/bundle.ts @@ -107,7 +107,7 @@ export function getBundleResult(program: ts.Program, files: ProcessedFile[]): [t // return require("") const args = options.luaTarget === LuaTarget.Lua50 ? "unpack(arg == nil and {} or arg)" : "..."; - const entryPoint = `return require(${createModulePath(entryModuleFilePath ?? entryModule, program)}, ${args}})\n`; + const entryPoint = `return require(${createModulePath(entryModuleFilePath ?? entryModule, program)}, ${args})\n`; const footers: string[] = []; if (options.sourceMapTraceback) { diff --git a/test/json.50.lua b/test/json.50.lua index bf6f06816..8e4109c8f 100644 --- a/test/json.50.lua +++ b/test/json.50.lua @@ -77,10 +77,12 @@ local function encode_table(val, stack) -- Treat as array -- check keys are valid and it is not sparse local n = 0 for k in pairs(val) do - if type(k) ~= "number" then - error("invalid table: mixed or invalid key types") + if k ~= "n" then + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 end - n = n + 1 end if n ~= table.getn(val) then error("invalid table: sparse array") @@ -107,7 +109,7 @@ end local function encode_string(val) - return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' + return '"' .. string.gsub(val, '[%z\1-\31\\"]', escape_char) .. '"' end diff --git a/test/transpile/lualib.spec.ts b/test/transpile/lualib.spec.ts index b35ab996b..20288bd8a 100644 --- a/test/transpile/lualib.spec.ts +++ b/test/transpile/lualib.spec.ts @@ -1,10 +1,10 @@ import * as ts from "typescript"; -import { LuaLibFeature } from "../../src"; +import { LuaLibFeature, LuaTarget } from "../../src"; import { readLuaLibFeature } from "../../src/LuaLib"; import * as util from "../util"; test.each(Object.entries(LuaLibFeature))("Lualib does not use ____exports (%p)", (_, feature) => { - const lualibCode = readLuaLibFeature(feature, ts.sys); + const lualibCode = readLuaLibFeature(feature, LuaTarget.Lua54, ts.sys); const exportsOccurrences = lualibCode.match(/____exports/g); expect(exportsOccurrences).toBeNull(); diff --git a/test/util.ts b/test/util.ts index 7bb9b5213..0ea1da714 100644 --- a/test/util.ts +++ b/test/util.ts @@ -9,10 +9,13 @@ import * as ts from "typescript"; import * as vm from "vm"; import * as tstl from "../src"; import { createEmitOutputCollector } from "../src/transpilation/output-collector"; -import { EmitHost, getEmitOutDir, transpileProject } from "../src"; +import { EmitHost, getEmitOutDir, LuaTarget, transpileProject } from "../src"; import { formatPathToLuaPath, normalizeSlashes } from "../src/utils"; -const luaLib = fs.readFileSync(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"), "utf8"); +function readLuaLib(target: tstl.LuaTarget) { + const luaLibDir = `../dist/${target === LuaTarget.Lua50 ? "lualib-lua50" : "lualib"}`; + return fs.readFileSync(path.resolve(__dirname, `${luaLibDir}/lualib_bundle.lua`), "utf8"); +} function jsonLib(target: tstl.LuaTarget): string { const fileName = target === tstl.LuaTarget.Lua50 ? "json.50.lua" : "json.lua"; @@ -425,7 +428,7 @@ export abstract class TestBuilder { this.options.luaLibImport === tstl.LuaLibImportKind.Require || mainFile.includes('require("lualib_bundle")') ) { - this.injectLuaFile(L, lua, lauxlib, "lualib_bundle", luaLib); + this.injectLuaFile(L, lua, lauxlib, "lualib_bundle", readLuaLib(luaTarget)); } // Load all transpiled files into Lua's package cache From e1871939a44b073941af2551a3a785f37965fa02 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Wed, 24 Aug 2022 08:45:50 +0000 Subject: [PATCH 36/69] chore: bump lua-wasm-bindings version --- package-lock.json | 38 +++++++++++++++++++++++++++++--------- package.json | 2 +- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0b12938aa..4d9acdf9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "jest": "^27.3.0", "jest-circus": "^27.3.0", "lua-types": "^2.11.0", - "lua-wasm-bindings": "^0.2.2", + "lua-wasm-bindings": "^0.3.0", "prettier": "^2.3.2", "ts-jest": "^27.1.3", "ts-node": "^10.3.0", @@ -1360,6 +1360,12 @@ "@types/node": "*" } }, + "node_modules/@types/semver": { + "version": "7.3.12", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.12.tgz", + "integrity": "sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -4812,10 +4818,14 @@ } }, "node_modules/lua-wasm-bindings": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/lua-wasm-bindings/-/lua-wasm-bindings-0.2.2.tgz", - "integrity": "sha512-Z2v3An8jEvYbhUJ/gYxNd65ZBbaRyPMmUaleDoOhzJSIGrMgjURvy1EtHtgRGTzdtpmKtHA+YYKDrSZBE9g/ig==", - "dev": true + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/lua-wasm-bindings/-/lua-wasm-bindings-0.3.0.tgz", + "integrity": "sha512-DLyAmU8/iLXj5zSU7DLL49/JzNXeSwDjtkgLscBDev9nDVrpQonhna5ZvnDpaEwrB19J3QWDALhfNsfHVHJwqw==", + "dev": true, + "dependencies": { + "@types/semver": "^7.3.9", + "semver": "^7.3.7" + } }, "node_modules/make-dir": { "version": "3.1.0", @@ -7399,6 +7409,12 @@ "@types/node": "*" } }, + "@types/semver": { + "version": "7.3.12", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.12.tgz", + "integrity": "sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -9981,10 +9997,14 @@ "requires": {} }, "lua-wasm-bindings": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/lua-wasm-bindings/-/lua-wasm-bindings-0.2.2.tgz", - "integrity": "sha512-Z2v3An8jEvYbhUJ/gYxNd65ZBbaRyPMmUaleDoOhzJSIGrMgjURvy1EtHtgRGTzdtpmKtHA+YYKDrSZBE9g/ig==", - "dev": true + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/lua-wasm-bindings/-/lua-wasm-bindings-0.3.0.tgz", + "integrity": "sha512-DLyAmU8/iLXj5zSU7DLL49/JzNXeSwDjtkgLscBDev9nDVrpQonhna5ZvnDpaEwrB19J3QWDALhfNsfHVHJwqw==", + "dev": true, + "requires": { + "@types/semver": "^7.3.9", + "semver": "^7.3.7" + } }, "make-dir": { "version": "3.1.0", diff --git a/package.json b/package.json index ead3c73a1..e40926427 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "jest": "^27.3.0", "jest-circus": "^27.3.0", "lua-types": "^2.11.0", - "lua-wasm-bindings": "^0.2.2", + "lua-wasm-bindings": "^0.3.0", "prettier": "^2.3.2", "ts-jest": "^27.1.3", "ts-node": "^10.3.0", From ccae8fd28feb3d79a5c236c737d96e5671b7979e Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 25 Aug 2022 00:35:01 +0000 Subject: [PATCH 37/69] fix(lualib): select() call was not using vararg optimization, breaking some spead cases --- src/lualib/CountVarargs.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lualib/CountVarargs.ts b/src/lualib/CountVarargs.ts index a97534aa4..060975d09 100644 --- a/src/lualib/CountVarargs.ts +++ b/src/lualib/CountVarargs.ts @@ -3,7 +3,9 @@ import { __TS__IsLua50 } from "./IsLua50"; export function __TS__CountVarargs(...args: T[]): number { - // In Lua 5.0, the arg table includes trailing nils. In all other - // versions, we must use select() to count trailing nils. - return __TS__IsLua50 ? args.length : select("#", ...args); + // select() is not available in Lua 5.0. In that version, the arg table + // includes trailing nils. + // It is important that the select() branch come first as we need vararg + // optimization for this call. + return !__TS__IsLua50 ? select("#", ...args) : args.length; } From 8d9ce98446cde7dc38e8fa3b988968c4dfb83bd8 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 25 Aug 2022 01:55:56 +0000 Subject: [PATCH 38/69] test: use inline lualib imports to get around problems with preloading the entire bundle --- test/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/util.ts b/test/util.ts index 0ea1da714..25236982c 100644 --- a/test/util.ts +++ b/test/util.ts @@ -68,7 +68,7 @@ export function testEachVersion( const testName = name === undefined ? version : `${name} [${version}]`; defineTest(testName, () => { const builder = common(); - builder.setOptions({ luaTarget: version }); + builder.setOptions({ luaTarget: version, luaLibImport: tstl.LuaLibImportKind.Inline }); if (typeof specialBuilder === "function") { specialBuilder(builder); } From d656050d3dccf43a7c86ee7f01f0c417d6a8f584 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 25 Aug 2022 18:40:02 +0000 Subject: [PATCH 39/69] test: remove old snapshots --- test/unit/__snapshots__/spread.spec.ts.snap | 198 -------------------- 1 file changed, 198 deletions(-) diff --git a/test/unit/__snapshots__/spread.spec.ts.snap b/test/unit/__snapshots__/spread.spec.ts.snap index 44731c5c3..d31bde3f1 100644 --- a/test/unit/__snapshots__/spread.spec.ts.snap +++ b/test/unit/__snapshots__/spread.spec.ts.snap @@ -16,201 +16,3 @@ function ____exports.__main(self) end return ____exports" `; - -exports[`vararg spread optimization With cast [5.0] 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local function pick(self, ...) - local args = arg - return args[2] - end - local function test(self, ...) - return pick( - nil, - unpack(arg) - ) - end - return test(nil, \\"a\\", \\"b\\", \\"c\\") -end -return ____exports" -`; - -exports[`vararg spread optimization basic use [5.0] 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local function pick(self, ...) - local args = arg - return args[2] - end - local function test(self, ...) - return pick( - nil, - unpack(arg) - ) - end - return test(nil, \\"a\\", \\"b\\", \\"c\\") -end -return ____exports" -`; - -exports[`vararg spread optimization block statement [5.0] 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local function pick(self, ...) - local args = arg - return args[2] - end - local function test(self, ...) - local result - do - result = pick( - nil, - unpack(arg) - ) - end - return result - end - return test(nil, \\"a\\", \\"b\\", \\"c\\") -end -return ____exports" -`; - -exports[`vararg spread optimization body-less arrow function [5.0] 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local function pick(self, ...) - local args = arg - return args[2] - end - local function test(____, ...) - return pick( - nil, - unpack(arg) - ) - end - return test(nil, \\"a\\", \\"b\\", \\"c\\") -end -return ____exports" -`; - -exports[`vararg spread optimization curry [5.0] 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local function test(self, fn, ...) - return fn( - nil, - unpack(arg) - ) - end - return test( - nil, - function(____, arg) return arg end, - \\"foobar\\" - ) -end -return ____exports" -`; - -exports[`vararg spread optimization curry with indirect type [5.0] 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local function test(self, obj, ...) - local fn = obj.fn - return fn( - nil, - unpack(arg) - ) - end - return test( - nil, - {fn = function(____, s) return s end}, - \\"foobar\\" - ) -end -return ____exports" -`; - -exports[`vararg spread optimization finally clause [5.0] 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local function pick(self, ...) - local args = arg - return args[2] - end - local function test(self, ...) - do - pcall(function() - error(\\"foobar\\", 0) - end) - do - return pick( - nil, - unpack(arg) - ) - end - end - end - return test(nil, \\"a\\", \\"b\\", \\"c\\") -end -return ____exports" -`; - -exports[`vararg spread optimization function type declared inside scope [5.0] 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local function test(self, ...) - local function fn(____, ...) - local args = arg - return args[1] - end - return fn( - nil, - unpack(arg) - ) - end - test(nil, \\"foobar\\") -end -return ____exports" -`; - -exports[`vararg spread optimization if statement [5.0] 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local function pick(self, ...) - local args = arg - return args[2] - end - local function test(self, ...) - if true then - return pick( - nil, - unpack(arg) - ) - end - end - return test(nil, \\"a\\", \\"b\\", \\"c\\") -end -return ____exports" -`; - -exports[`vararg spread optimization loop statement [5.0] 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local function pick(self, ...) - local args = arg - return args[2] - end - local function test(self, ...) - repeat - do - return pick( - nil, - unpack(arg) - ) - end - until not false - end - return test(nil, \\"a\\", \\"b\\", \\"c\\") -end -return ____exports" -`; From 581fb52f3a0b9ecb98a4a2444af9730df45fc2e2 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 25 Aug 2022 18:42:27 +0000 Subject: [PATCH 40/69] fix: json files can't contain comments --- src/lualib-lua50/tsconfig.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lualib-lua50/tsconfig.json b/src/lualib-lua50/tsconfig.json index 7cbfe09eb..a054a05ca 100644 --- a/src/lualib-lua50/tsconfig.json +++ b/src/lualib-lua50/tsconfig.json @@ -3,8 +3,6 @@ "outDir": "../../dist/lualib-lua50", "target": "esnext", "lib": ["esnext"], - // Counterintuitively, we do not build with the 5.0 types as they are incompatible with - // branches written for the universal target. For now, the 5.4 types work for both targets. "types": ["lua-types/5.4"], "skipLibCheck": true, From ab49baeb94eed0e76f3a713f91274286c967a599 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 25 Aug 2022 18:53:28 +0000 Subject: [PATCH 41/69] Revert "fix: json files can't contain comments" This reverts commit 581fb52f3a0b9ecb98a4a2444af9730df45fc2e2. Turns out they CAN contain comments, when they're named tsconfig.json! --- src/lualib-lua50/tsconfig.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lualib-lua50/tsconfig.json b/src/lualib-lua50/tsconfig.json index a054a05ca..7cbfe09eb 100644 --- a/src/lualib-lua50/tsconfig.json +++ b/src/lualib-lua50/tsconfig.json @@ -3,6 +3,8 @@ "outDir": "../../dist/lualib-lua50", "target": "esnext", "lib": ["esnext"], + // Counterintuitively, we do not build with the 5.0 types as they are incompatible with + // branches written for the universal target. For now, the 5.4 types work for both targets. "types": ["lua-types/5.4"], "skipLibCheck": true, From 38b0288227aa213e0a798f761cce36731af1bfbd Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 27 Aug 2022 22:35:54 +0000 Subject: [PATCH 42/69] test: exclude lualib-lua50 from coverage --- jest.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jest.config.js b/jest.config.js index 2d390c93d..44b6bb379 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,6 +6,7 @@ module.exports = { collectCoverageFrom: [ "/src/**/*", "!/src/lualib/**/*", + "!/src/lualib-lua50/**/*", // https://github.com/facebook/jest/issues/5274 "!/src/tstl.ts", ], From ced3532e7e16e9f13ce62e5f0a1b73960d1be308 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Mon, 29 Aug 2022 00:06:38 +0000 Subject: [PATCH 43/69] style: move 5.0 tsconfig.json to existing lualib dir --- package.json | 2 +- src/{lualib-lua50/tsconfig.json => lualib/tsconfig.lua50.json} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{lualib-lua50/tsconfig.json => lualib/tsconfig.lua50.json} (90%) diff --git a/package.json b/package.json index e40926427..0d40231c7 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "types": "dist/index.d.ts", "scripts": { "build": "tsc && npm run build-lualib", - "build-lualib": "node dist/tstl.js -p src/lualib/tsconfig.json && node dist/tstl.js -p src/lualib-lua50/tsconfig.json", + "build-lualib": "node dist/tstl.js -p src/lualib/tsconfig.json && node dist/tstl.js -p src/lualib/tsconfig.lua50.json", "pretest": "npm run lint && npm run check:language-extensions && npm run build-lualib", "test": "jest", "lint": "npm run lint:eslint && npm run lint:prettier", diff --git a/src/lualib-lua50/tsconfig.json b/src/lualib/tsconfig.lua50.json similarity index 90% rename from src/lualib-lua50/tsconfig.json rename to src/lualib/tsconfig.lua50.json index 7cbfe09eb..bebe36c06 100644 --- a/src/lualib-lua50/tsconfig.json +++ b/src/lualib/tsconfig.lua50.json @@ -17,5 +17,5 @@ "luaTarget": "5.0", "luaPlugins": [{ "name": "../../dist/lualib-build/plugin.js" }] }, - "include": ["../lualib", "../../language-extensions/index.d.ts"] + "include": [".", "../../language-extensions/index.d.ts"] } From 28cc8cdd5be3ef523c9bc8905676fc02c51755e0 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Mon, 29 Aug 2022 00:57:54 +0000 Subject: [PATCH 44/69] style: refactor lualib resolution into common function --- src/LuaLib.ts | 14 ++++++++------ test/util.ts | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/LuaLib.ts b/src/LuaLib.ts index e5f3d1baa..cf7affbb6 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -112,12 +112,16 @@ export interface LuaLibFeatureInfo { } export type LuaLibModulesInfo = Record; +export function resolveLuaLibDir(luaTarget: LuaTarget) { + const luaLibDir = luaTarget === LuaTarget.Lua50 ? "lualib-lua50" : "lualib"; + return path.resolve(__dirname, path.join("..", "dist", luaLibDir)); +} + export const luaLibModulesInfoFileName = "lualib_module_info.json"; let luaLibModulesInfo: LuaLibModulesInfo | undefined; export function getLuaLibModulesInfo(luaTarget: LuaTarget, emitHost: EmitHost): LuaLibModulesInfo { if (luaLibModulesInfo === undefined) { - const lualibDir = `../dist/${luaTarget === LuaTarget.Lua50 ? "lualib-lua50" : "lualib"}`; - const lualibPath = path.resolve(__dirname, `${lualibDir}/${luaLibModulesInfoFileName}`); + const lualibPath = path.join(resolveLuaLibDir(luaTarget), luaLibModulesInfoFileName); const result = emitHost.readFile(lualibPath); if (result !== undefined) { luaLibModulesInfo = JSON.parse(result) as LuaLibModulesInfo; @@ -129,8 +133,7 @@ export function getLuaLibModulesInfo(luaTarget: LuaTarget, emitHost: EmitHost): } export function readLuaLibFeature(feature: LuaLibFeature, luaTarget: LuaTarget, emitHost: EmitHost): string { - const lualibDir = `../dist/${luaTarget === LuaTarget.Lua50 ? "lualib-lua50" : "lualib"}`; - const featurePath = path.resolve(__dirname, `${lualibDir}/${feature}.lua`); + const featurePath = path.join(resolveLuaLibDir(luaTarget), `${feature}.lua`); const luaLibFeature = emitHost.readFile(featurePath); if (luaLibFeature === undefined) { throw new Error(`Could not load lualib feature from '${featurePath}'`); @@ -215,8 +218,7 @@ export function loadImportedLualibFeatures( let luaLibBundleContent: string; export function getLuaLibBundle(luaTarget: LuaTarget, emitHost: EmitHost): string { if (luaLibBundleContent === undefined) { - const lualibDir = `../dist/${luaTarget === LuaTarget.Lua50 ? "lualib-lua50" : "lualib"}`; - const lualibPath = path.resolve(__dirname, `${lualibDir}/lualib_bundle.lua`); + const lualibPath = path.join(resolveLuaLibDir(luaTarget), "lualib_bundle.lua"); const result = emitHost.readFile(lualibPath); if (result !== undefined) { luaLibBundleContent = result; diff --git a/test/util.ts b/test/util.ts index 25236982c..0595224d4 100644 --- a/test/util.ts +++ b/test/util.ts @@ -9,12 +9,12 @@ import * as ts from "typescript"; import * as vm from "vm"; import * as tstl from "../src"; import { createEmitOutputCollector } from "../src/transpilation/output-collector"; -import { EmitHost, getEmitOutDir, LuaTarget, transpileProject } from "../src"; +import { EmitHost, getEmitOutDir, transpileProject } from "../src"; import { formatPathToLuaPath, normalizeSlashes } from "../src/utils"; +import { resolveLuaLibDir } from "../src/LuaLib"; function readLuaLib(target: tstl.LuaTarget) { - const luaLibDir = `../dist/${target === LuaTarget.Lua50 ? "lualib-lua50" : "lualib"}`; - return fs.readFileSync(path.resolve(__dirname, `${luaLibDir}/lualib_bundle.lua`), "utf8"); + return fs.readFileSync(path.join(resolveLuaLibDir(target), "lualib_bundle.lua"), "utf8"); } function jsonLib(target: tstl.LuaTarget): string { From d1bf66a73f8ec284d93cd6de239847d699c2c566 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Mon, 29 Aug 2022 01:50:30 +0000 Subject: [PATCH 45/69] style: factor out table length transpilation into common function --- src/transformation/builtins/array.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/transformation/builtins/array.ts b/src/transformation/builtins/array.ts index 8c83475bb..c9074874f 100644 --- a/src/transformation/builtins/array.ts +++ b/src/transformation/builtins/array.ts @@ -30,7 +30,17 @@ export function transformArrayConstructorCall( } } -const lua50TableLength = lua.createTableIndexExpression(lua.createIdentifier("table"), lua.createStringLiteral("getn")); +function createTableLengthExpression(context: TransformationContext, expression: lua.Expression, node?: ts.Expression) { + if (context.luaTarget === LuaTarget.Lua50) { + const tableGetn = lua.createTableIndexExpression( + lua.createIdentifier("table"), + lua.createStringLiteral("getn") + ); + return lua.createCallExpression(tableGetn, [expression], node); + } else { + return lua.createUnaryExpression(expression, lua.SyntaxKind.LengthOperator, node); + } +} /** * Optimized single element Array.push @@ -50,9 +60,7 @@ function transformSingleElementArrayPush( // #array + 1 let lengthExpression: lua.Expression = lua.createBinaryExpression( - context.luaTarget === LuaTarget.Lua50 - ? lua.createCallExpression(lua50TableLength, [arrayIdentifier]) - : lua.createUnaryExpression(arrayIdentifier, lua.SyntaxKind.LengthOperator), + createTableLengthExpression(context, arrayIdentifier), lua.createNumericLiteral(1), lua.SyntaxKind.AdditionOperator ); @@ -189,9 +197,7 @@ export function transformArrayProperty( switch (node.name.text) { case "length": const expression = context.transformExpression(node.expression); - return context.luaTarget === LuaTarget.Lua50 - ? lua.createCallExpression(lua50TableLength, [expression], node) - : lua.createUnaryExpression(expression, lua.SyntaxKind.LengthOperator, node); + return createTableLengthExpression(context, expression, node); default: return undefined; } From f2563cc76ddd7a638a71e437448faec4669f1aed Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Mon, 29 Aug 2022 01:59:41 +0000 Subject: [PATCH 46/69] style: format 5.0 and non-5.0 require shims --- src/transpilation/bundle.ts | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/transpilation/bundle.ts b/src/transpilation/bundle.ts index d018c1d86..d109fe3d0 100644 --- a/src/transpilation/bundle.ts +++ b/src/transpilation/bundle.ts @@ -13,8 +13,8 @@ const createModulePath = (pathToResolve: string, program: ts.Program) => // Override `require` to read from ____modules table. function requireOverride(options: CompilerOptions) { - const isLua50 = options.luaTarget === LuaTarget.Lua50; - return ` + if (options.luaTarget === LuaTarget.Lua50) { + return ` local ____modules = {} local ____moduleCache = {} local ____originalRequire = require @@ -24,9 +24,7 @@ local function require(file, ...) end if ____modules[file] then local module = ____modules[file] - ____moduleCache[file] = { value = (${isLua50 ? "table.getn(arg)" : 'select("#", ...)'} > 0) and module(${ - isLua50 ? "unpack(arg)" : "..." - }) or module(file) } + ____moduleCache[file] = { value = (table.getn(arg) > 0) and module(unpack(arg)) or module(file) } return ____moduleCache[file].value else if ____originalRequire then @@ -37,6 +35,29 @@ local function require(file, ...) end end `; + } else { + return ` +local ____modules = {} +local ____moduleCache = {} +local ____originalRequire = require +local function require(file, ...) + if ____moduleCache[file] then + return ____moduleCache[file].value + end + if ____modules[file] then + local module = ____modules[file] + ____moduleCache[file] = { value = (select("#", ...) > 0) and module(...) or module(file) } + return ____moduleCache[file].value + else + if ____originalRequire then + return ____originalRequire(file) + else + error("module '" .. file .. "' not found") + end + end +end +`; + } } export const sourceMapTracebackBundlePlaceholder = "{#SourceMapTracebackBundle}"; From 4ab5891c38f84773ef3e70c7b35985581267c7c1 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Mon, 29 Aug 2022 02:15:00 +0000 Subject: [PATCH 47/69] test: remove obsolete lua 5.0 arg test --- test/unit/spread.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/unit/spread.spec.ts b/test/unit/spread.spec.ts index 813e57754..40f650055 100644 --- a/test/unit/spread.spec.ts +++ b/test/unit/spread.spec.ts @@ -4,7 +4,6 @@ import { formatCode } from "../util"; // TODO: Make some utils for testing other targets const expectUnpack: util.TapCallback = builder => expect(builder.getMainLuaCodeChunk()).toMatch(/[^.]unpack\(/); -const expectArg: util.TapCallback = builder => expect(builder.getMainLuaCodeChunk()).toMatch(/ arg/); const expectTableUnpack: util.TapCallback = builder => expect(builder.getMainLuaCodeChunk()).toContain("table.unpack"); const expectLualibUnpack: util.TapCallback = builder => expect(builder.getMainLuaCodeChunk()).toContain("__TS__Unpack"); @@ -77,7 +76,7 @@ describe("in function call", () => { { [tstl.LuaTarget.Universal]: builder => builder.tap(expectLualibUnpack).expectToMatchJsResult(), [tstl.LuaTarget.LuaJIT]: builder => builder.tap(expectUnpack), - [tstl.LuaTarget.Lua50]: builder => builder.tap(expectArg).expectToMatchJsResult(), + [tstl.LuaTarget.Lua50]: builder => builder.tap(expectUnpack).expectToMatchJsResult(), [tstl.LuaTarget.Lua51]: builder => builder.tap(expectUnpack).expectToMatchJsResult(), [tstl.LuaTarget.Lua52]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(), [tstl.LuaTarget.Lua53]: builder => builder.tap(expectTableUnpack).expectToMatchJsResult(), From fdcea36ba61659580a8f53c8734b97c391ba2104 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Mon, 29 Aug 2022 06:20:34 +0000 Subject: [PATCH 48/69] refactor(lualib): use builtins for the 5.0 branches instead of a version check --- src/LuaLib.ts | 1 - src/lualib/CountVarargs.ts | 4 +--- src/lualib/IsLua50.ts | 4 ---- src/lualib/Match.ts | 8 +++----- src/lualib/NumberToString.ts | 8 +++----- src/lualib/SourceMapTraceBack.ts | 4 ++-- 6 files changed, 9 insertions(+), 20 deletions(-) delete mode 100644 src/lualib/IsLua50.ts diff --git a/src/LuaLib.ts b/src/LuaLib.ts index cf7affbb6..4ce7d2baa 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -45,7 +45,6 @@ export enum LuaLibFeature { Generator = "Generator", InstanceOf = "InstanceOf", InstanceOfObject = "InstanceOfObject", - IsLua50 = "IsLua50", Iterator = "Iterator", LuaIteratorSpread = "LuaIteratorSpread", Map = "Map", diff --git a/src/lualib/CountVarargs.ts b/src/lualib/CountVarargs.ts index 060975d09..e012a97b9 100644 --- a/src/lualib/CountVarargs.ts +++ b/src/lualib/CountVarargs.ts @@ -1,11 +1,9 @@ /** @noSelfInFile */ -import { __TS__IsLua50 } from "./IsLua50"; - export function __TS__CountVarargs(...args: T[]): number { // select() is not available in Lua 5.0. In that version, the arg table // includes trailing nils. // It is important that the select() branch come first as we need vararg // optimization for this call. - return !__TS__IsLua50 ? select("#", ...args) : args.length; + return select ? select("#", ...args) : args.length; } diff --git a/src/lualib/IsLua50.ts b/src/lualib/IsLua50.ts deleted file mode 100644 index fd6e8196a..000000000 --- a/src/lualib/IsLua50.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** @noSelfInFile */ - -export const __TS__IsLua50 = - _VERSION === "Lua 5.0" || _VERSION === "Lua 5.0.1" || _VERSION === "Lua 5.0.2" || _VERSION === "Lua 5.0.3"; diff --git a/src/lualib/Match.ts b/src/lualib/Match.ts index d800097a7..9517151a1 100644 --- a/src/lualib/Match.ts +++ b/src/lualib/Match.ts @@ -1,9 +1,9 @@ /** @noSelfInFile */ -import { __TS__IsLua50 } from "./IsLua50"; - export function __TS__Match(s: string, pattern: string, init?: number): LuaMultiReturn { - if (__TS__IsLua50) { + if (string.match) { + return string.match(s, pattern, init); + } else { const [start, end, ...captures] = string.find(s, pattern, init); if (start === undefined || end === undefined) { return $multi(); @@ -12,7 +12,5 @@ export function __TS__Match(s: string, pattern: string, init?: number): LuaMulti } else { return $multi(...(captures as string[])); } - } else { - return string.match(s, pattern, init); } } diff --git a/src/lualib/NumberToString.ts b/src/lualib/NumberToString.ts index 349771c52..538a1b3d9 100644 --- a/src/lualib/NumberToString.ts +++ b/src/lualib/NumberToString.ts @@ -1,13 +1,11 @@ -import { __TS__IsLua50 } from "./IsLua50"; - const radixChars = "0123456789abcdefghijklmnopqrstuvwxyz"; function modf(this: void, x: number): LuaMultiReturn<[number, number]> { - if (__TS__IsLua50) { + if (math.modf) { + return math.modf(x); + } else { const integral = x > 0 ? Math.floor(x) : Math.ceil(x); return $multi(integral, x - integral); - } else { - return math.modf(x); } } diff --git a/src/lualib/SourceMapTraceBack.ts b/src/lualib/SourceMapTraceBack.ts index 9bb741e20..7f1055a2d 100644 --- a/src/lualib/SourceMapTraceBack.ts +++ b/src/lualib/SourceMapTraceBack.ts @@ -1,7 +1,6 @@ // TODO: In the future, change this to __TS__RegisterFileInfo and provide tstl interface to // get some metadata about transpilation. -import { __TS__IsLua50 } from "./IsLua50"; import { __TS__Match } from "./Match"; interface SourceMap { @@ -21,7 +20,8 @@ export function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap let trace: string; if (thread === undefined && message === undefined && level === undefined) { trace = originalTraceback(); - } else if (__TS__IsLua50) { + } else if (string.sub(_VERSION, 7, 7) === "0") { + // For Lua 5.0, which doesn't support the thread argument. trace = originalTraceback(message, level); } else { trace = originalTraceback(thread, message, level); From f390622c5b7d50376cb5d121a957162c55f31657 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Tue, 30 Aug 2022 19:49:02 +0000 Subject: [PATCH 49/69] refactor(lualib): move 5.0-specific to its own directory --- src/lualib-build/plugin.ts | 10 +++ src/lualib/5.0/CountVarargs.ts | 7 ++ src/lualib/5.0/Error.ts | 71 +++++++++++++++++++ src/lualib/5.0/Generator.ts | 28 ++++++++ src/lualib/5.0/Match.ts | 12 ++++ src/lualib/{ => 5.0}/NumberToString.ts | 8 +-- src/lualib/5.0/SourceMapTraceBack.ts | 69 ++++++++++++++++++ src/lualib/5.0/SparseArraySpread.ts | 5 ++ src/lualib/5.0/Unpack.ts | 1 + src/lualib/Match.ts | 16 ----- src/lualib/tsconfig.json | 3 +- src/lualib/tsconfig.lua50.json | 7 +- src/lualib/{ => universal}/CountVarargs.ts | 4 +- src/lualib/{ => universal}/Error.ts | 0 src/lualib/{ => universal}/Generator.ts | 0 src/lualib/universal/Match.ts | 5 ++ src/lualib/universal/NumberToString.ts | 47 ++++++++++++ .../{ => universal}/SourceMapTraceBack.ts | 3 - .../{ => universal}/SparseArraySpread.ts | 0 src/lualib/{ => universal}/Unpack.ts | 0 20 files changed, 263 insertions(+), 33 deletions(-) create mode 100644 src/lualib/5.0/CountVarargs.ts create mode 100644 src/lualib/5.0/Error.ts create mode 100644 src/lualib/5.0/Generator.ts create mode 100644 src/lualib/5.0/Match.ts rename src/lualib/{ => 5.0}/NumberToString.ts (89%) create mode 100644 src/lualib/5.0/SourceMapTraceBack.ts create mode 100644 src/lualib/5.0/SparseArraySpread.ts create mode 100644 src/lualib/5.0/Unpack.ts delete mode 100644 src/lualib/Match.ts rename src/lualib/{ => universal}/CountVarargs.ts (54%) rename src/lualib/{ => universal}/Error.ts (100%) rename src/lualib/{ => universal}/Generator.ts (100%) create mode 100644 src/lualib/universal/Match.ts create mode 100644 src/lualib/universal/NumberToString.ts rename src/lualib/{ => universal}/SourceMapTraceBack.ts (93%) rename src/lualib/{ => universal}/SparseArraySpread.ts (100%) rename src/lualib/{ => universal}/Unpack.ts (100%) diff --git a/src/lualib-build/plugin.ts b/src/lualib-build/plugin.ts index 3cf1d3d14..5c65ad546 100644 --- a/src/lualib-build/plugin.ts +++ b/src/lualib-build/plugin.ts @@ -42,6 +42,16 @@ class LuaLibPlugin implements tstl.Plugin { emitBOM ); + // Flatten the output folder structure; we do not want to keep the target-specific directories + for (const file of result) { + let outPath = file.fileName; + while (outPath.includes("lualib") && path.basename(path.dirname(outPath)) !== "lualib") { + const upOne = path.join(path.dirname(outPath), "..", path.basename(outPath)); + outPath = path.normalize(upOne); + } + file.fileName = outPath; + } + // Create map of result files keyed by their 'lualib name' const exportedLualibFeatures = new Map(result.map(f => [path.basename(f.fileName).split(".")[0], f.code])); diff --git a/src/lualib/5.0/CountVarargs.ts b/src/lualib/5.0/CountVarargs.ts new file mode 100644 index 000000000..34467ebd4 --- /dev/null +++ b/src/lualib/5.0/CountVarargs.ts @@ -0,0 +1,7 @@ +/** @noSelfInFile */ + +export function __TS__CountVarargs(...args: T[]): number { + // select() is not available in Lua 5.0. In this version, the arg table + // includes trailing nils. + return args.length; +} diff --git a/src/lualib/5.0/Error.ts b/src/lualib/5.0/Error.ts new file mode 100644 index 000000000..29a478a12 --- /dev/null +++ b/src/lualib/5.0/Error.ts @@ -0,0 +1,71 @@ +interface ErrorType { + name: string; + new (...args: any[]): Error; +} + +function getErrorStack(constructor: () => any): string { + let level = 1; + while (true) { + const info = debug.getinfo(level, "f"); + level += 1; + if (!info) { + // constructor is not in call stack + level = 1; + break; + } else if (info.func === constructor) { + break; + } + } + + return debug.traceback(`[Level ${level}]`); +} + +function wrapErrorToString(getDescription: (this: T) => string): (this: T) => string { + return function (this: Error): string { + const description = getDescription.call(this); + return description; + }; +} + +function initErrorClass(Type: ErrorType, name: string): any { + Type.name = name; + return setmetatable(Type, { + __call: (_self: any, message: string) => new Type(message), + }); +} + +export const Error: ErrorConstructor = initErrorClass( + class implements Error { + public name = "Error"; + public stack: string; + + constructor(public message = "") { + this.stack = getErrorStack((this.constructor as any).new); + const metatable = getmetatable(this); + if (!metatable.__errorToStringPatched) { + metatable.__errorToStringPatched = true; + metatable.__tostring = wrapErrorToString(metatable.__tostring); + } + } + + public toString(): string { + return this.message !== "" ? `${this.name}: ${this.message}` : this.name; + } + }, + "Error" +); + +function createErrorClass(name: string) { + return initErrorClass( + class extends Error { + public name = name; + }, + name + ); +} + +export const RangeError = createErrorClass("RangeError"); +export const ReferenceError = createErrorClass("ReferenceError"); +export const SyntaxError = createErrorClass("SyntaxError"); +export const TypeError = createErrorClass("TypeError"); +export const URIError = createErrorClass("URIError"); diff --git a/src/lualib/5.0/Generator.ts b/src/lualib/5.0/Generator.ts new file mode 100644 index 000000000..082266119 --- /dev/null +++ b/src/lualib/5.0/Generator.ts @@ -0,0 +1,28 @@ +import { __TS__CountVarargs } from "./CountVarargs"; +import { GeneratorIterator } from "./GeneratorIterator"; + +function generatorIterator(this: GeneratorIterator) { + return this; +} + +function generatorNext(this: GeneratorIterator, ...args: any[]) { + const co = this.____coroutine; + if (coroutine.status(co) === "dead") return { done: true }; + + const [status, value] = coroutine.resume(co, ...args); + if (!status) throw value; + + return { value, done: coroutine.status(co) === "dead" }; +} + +export function __TS__Generator(this: void, fn: (this: void, ...args: any[]) => any) { + return function (this: void, ...args: any[]): GeneratorIterator { + const argsLength = __TS__CountVarargs(...args); + return { + // Using explicit this there, since we don't pass arguments after the first nil and context is likely to be nil + ____coroutine: coroutine.create(() => fn(...unpack(args, 1, argsLength))), + [Symbol.iterator]: generatorIterator, + next: generatorNext, + }; + }; +} diff --git a/src/lualib/5.0/Match.ts b/src/lualib/5.0/Match.ts new file mode 100644 index 000000000..3b1705939 --- /dev/null +++ b/src/lualib/5.0/Match.ts @@ -0,0 +1,12 @@ +/** @noSelfInFile */ + +export function __TS__Match(s: string, pattern: string, init?: number): LuaMultiReturn { + const [start, end, ...captures] = string.find(s, pattern, init); + if (start === undefined || end === undefined) { + return $multi(); + } else if (captures.length <= 0) { + return $multi(s.slice(start - 1, end)); + } else { + return $multi(...(captures as string[])); + } +} diff --git a/src/lualib/NumberToString.ts b/src/lualib/5.0/NumberToString.ts similarity index 89% rename from src/lualib/NumberToString.ts rename to src/lualib/5.0/NumberToString.ts index 538a1b3d9..a265bc3d9 100644 --- a/src/lualib/NumberToString.ts +++ b/src/lualib/5.0/NumberToString.ts @@ -1,12 +1,8 @@ const radixChars = "0123456789abcdefghijklmnopqrstuvwxyz"; function modf(this: void, x: number): LuaMultiReturn<[number, number]> { - if (math.modf) { - return math.modf(x); - } else { - const integral = x > 0 ? Math.floor(x) : Math.ceil(x); - return $multi(integral, x - integral); - } + const integral = x > 0 ? Math.floor(x) : Math.ceil(x); + return $multi(integral, x - integral); } // https://www.ecma-international.org/ecma-262/10.0/index.html#sec-number.prototype.tostring diff --git a/src/lualib/5.0/SourceMapTraceBack.ts b/src/lualib/5.0/SourceMapTraceBack.ts new file mode 100644 index 000000000..25c23e9c6 --- /dev/null +++ b/src/lualib/5.0/SourceMapTraceBack.ts @@ -0,0 +1,69 @@ +// TODO: In the future, change this to __TS__RegisterFileInfo and provide tstl interface to +// get some metadata about transpilation. + +import { __TS__Match } from "./Match"; + +interface SourceMap { + [line: number]: number | { line: number; file: string }; +} + +declare function __TS__originalTraceback(this: void, thread?: LuaThread, message?: string, level?: number); + +export function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap: SourceMap): void { + globalThis.__TS__sourcemap = globalThis.__TS__sourcemap || {}; + globalThis.__TS__sourcemap[fileName] = sourceMap; + + if (globalThis.__TS__originalTraceback === undefined) { + const originalTraceback = debug.traceback; + globalThis.__TS__originalTraceback = originalTraceback; + debug.traceback = ((thread, message, level) => { + let trace: string; + if (thread === undefined && message === undefined && level === undefined) { + trace = originalTraceback(); + } else { + trace = originalTraceback(`[Level ${level}] ${message}`); + } + + if (typeof trace !== "string") { + return trace; + } + + const replacer = (file: string, srcFile: string, line: string) => { + const fileSourceMap: SourceMap = globalThis.__TS__sourcemap[file]; + if (fileSourceMap && fileSourceMap[line]) { + const data = fileSourceMap[line]; + if (typeof data === "number") { + return `${srcFile}:${data}`; + } + + return `${data.file}:${data.line}`; + } + + return `${file}:${line}`; + }; + + let [result] = string.gsub(trace, "(%S+)%.lua:(%d+)", (file, line) => + replacer(`${file}.lua`, `${file}.ts`, line) + ); + + const stringReplacer = (file: string, line: string) => { + const fileSourceMap: SourceMap = globalThis.__TS__sourcemap[file]; + if (fileSourceMap && fileSourceMap[line]) { + const chunkName = __TS__Match(file, '%[string "([^"]+)"%]')[0]; + const [sourceName] = string.gsub(chunkName, ".lua$", ".ts"); + const data = fileSourceMap[line]; + if (typeof data === "number") { + return `${sourceName}:${data}`; + } + + return `${data.file}:${data.line}`; + } + + return `${file}:${line}`; + }; + [result] = string.gsub(result, '(%[string "[^"]+"%]):(%d+)', (file, line) => stringReplacer(file, line)); + + return result; + }) as typeof debug.traceback; + } +} diff --git a/src/lualib/5.0/SparseArraySpread.ts b/src/lualib/5.0/SparseArraySpread.ts new file mode 100644 index 000000000..e404bac47 --- /dev/null +++ b/src/lualib/5.0/SparseArraySpread.ts @@ -0,0 +1,5 @@ +import { __TS__SparseArray } from "./SparseArray"; + +export function __TS__SparseArraySpread(this: void, sparseArray: __TS__SparseArray): LuaMultiReturn { + return unpack(sparseArray, 1, sparseArray.sparseLength); +} diff --git a/src/lualib/5.0/Unpack.ts b/src/lualib/5.0/Unpack.ts new file mode 100644 index 000000000..6d1ef8edd --- /dev/null +++ b/src/lualib/5.0/Unpack.ts @@ -0,0 +1 @@ +export const __TS__Unpack = unpack; diff --git a/src/lualib/Match.ts b/src/lualib/Match.ts deleted file mode 100644 index 9517151a1..000000000 --- a/src/lualib/Match.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** @noSelfInFile */ - -export function __TS__Match(s: string, pattern: string, init?: number): LuaMultiReturn { - if (string.match) { - return string.match(s, pattern, init); - } else { - const [start, end, ...captures] = string.find(s, pattern, init); - if (start === undefined || end === undefined) { - return $multi(); - } else if (captures.length <= 0) { - return $multi(s.slice(start - 1, end)); - } else { - return $multi(...(captures as string[])); - } - } -} diff --git a/src/lualib/tsconfig.json b/src/lualib/tsconfig.json index 246e46b90..ef50543c6 100644 --- a/src/lualib/tsconfig.json +++ b/src/lualib/tsconfig.json @@ -5,6 +5,7 @@ "lib": ["esnext"], "types": ["lua-types/5.4"], "skipLibCheck": true, + "rootDirs": [".", "universal"], "noUnusedLocals": true, "noUnusedParameters": true @@ -14,5 +15,5 @@ "noHeader": true, "luaPlugins": [{ "name": "../../dist/lualib-build/plugin.js" }] }, - "include": [".", "../../language-extensions/index.d.ts"] + "include": ["*.ts", "universal/*.ts", "declarations", "../../language-extensions/index.d.ts"] } diff --git a/src/lualib/tsconfig.lua50.json b/src/lualib/tsconfig.lua50.json index bebe36c06..b49ea6daa 100644 --- a/src/lualib/tsconfig.lua50.json +++ b/src/lualib/tsconfig.lua50.json @@ -3,10 +3,9 @@ "outDir": "../../dist/lualib-lua50", "target": "esnext", "lib": ["esnext"], - // Counterintuitively, we do not build with the 5.0 types as they are incompatible with - // branches written for the universal target. For now, the 5.4 types work for both targets. - "types": ["lua-types/5.4"], + "types": ["lua-types/5.0"], "skipLibCheck": true, + "rootDirs": [".", "5.0"], "noUnusedLocals": true, "noUnusedParameters": true @@ -17,5 +16,5 @@ "luaTarget": "5.0", "luaPlugins": [{ "name": "../../dist/lualib-build/plugin.js" }] }, - "include": [".", "../../language-extensions/index.d.ts"] + "include": ["*.ts", "5.0/*.ts", "declarations", "../../language-extensions/index.d.ts"] } diff --git a/src/lualib/CountVarargs.ts b/src/lualib/universal/CountVarargs.ts similarity index 54% rename from src/lualib/CountVarargs.ts rename to src/lualib/universal/CountVarargs.ts index e012a97b9..2441b0447 100644 --- a/src/lualib/CountVarargs.ts +++ b/src/lualib/universal/CountVarargs.ts @@ -1,9 +1,7 @@ /** @noSelfInFile */ export function __TS__CountVarargs(...args: T[]): number { - // select() is not available in Lua 5.0. In that version, the arg table - // includes trailing nils. // It is important that the select() branch come first as we need vararg // optimization for this call. - return select ? select("#", ...args) : args.length; + return select("#", ...args); } diff --git a/src/lualib/Error.ts b/src/lualib/universal/Error.ts similarity index 100% rename from src/lualib/Error.ts rename to src/lualib/universal/Error.ts diff --git a/src/lualib/Generator.ts b/src/lualib/universal/Generator.ts similarity index 100% rename from src/lualib/Generator.ts rename to src/lualib/universal/Generator.ts diff --git a/src/lualib/universal/Match.ts b/src/lualib/universal/Match.ts new file mode 100644 index 000000000..ebf72d154 --- /dev/null +++ b/src/lualib/universal/Match.ts @@ -0,0 +1,5 @@ +/** @noSelfInFile */ + +export function __TS__Match(s: string, pattern: string, init?: number): LuaMultiReturn { + return string.match(s, pattern, init); +} diff --git a/src/lualib/universal/NumberToString.ts b/src/lualib/universal/NumberToString.ts new file mode 100644 index 000000000..1e1a1ad03 --- /dev/null +++ b/src/lualib/universal/NumberToString.ts @@ -0,0 +1,47 @@ +const radixChars = "0123456789abcdefghijklmnopqrstuvwxyz"; + +// https://www.ecma-international.org/ecma-262/10.0/index.html#sec-number.prototype.tostring +export function __TS__NumberToString(this: number, radix?: number): string { + if (radix === undefined || radix === 10 || this === Infinity || this === -Infinity || this !== this) { + return this.toString(); + } + + radix = Math.floor(radix); + if (radix < 2 || radix > 36) { + throw "toString() radix argument must be between 2 and 36"; + } + + let [integer, fraction] = math.modf(Math.abs(this)); + + let result = ""; + if (radix === 8) { + result = string.format("%o", integer); + } else if (radix === 16) { + result = string.format("%x", integer); + } else { + do { + result = radixChars[integer % radix] + result; + integer = Math.floor(integer / radix); + } while (integer !== 0); + } + + // https://github.com/v8/v8/blob/f78e8d43c224847fa56b3220a90be250fc0f0d6e/src/numbers/conversions.cc#L1221 + if (fraction !== 0) { + result += "."; + let delta = 1e-16; + do { + fraction *= radix; + delta *= radix; + const digit = Math.floor(fraction); + result += radixChars[digit]; + fraction -= digit; + // TODO: Round to even + } while (fraction >= delta); + } + + if (this < 0) { + result = "-" + result; + } + + return result; +} diff --git a/src/lualib/SourceMapTraceBack.ts b/src/lualib/universal/SourceMapTraceBack.ts similarity index 93% rename from src/lualib/SourceMapTraceBack.ts rename to src/lualib/universal/SourceMapTraceBack.ts index 7f1055a2d..4022a7e00 100644 --- a/src/lualib/SourceMapTraceBack.ts +++ b/src/lualib/universal/SourceMapTraceBack.ts @@ -20,9 +20,6 @@ export function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap let trace: string; if (thread === undefined && message === undefined && level === undefined) { trace = originalTraceback(); - } else if (string.sub(_VERSION, 7, 7) === "0") { - // For Lua 5.0, which doesn't support the thread argument. - trace = originalTraceback(message, level); } else { trace = originalTraceback(thread, message, level); } diff --git a/src/lualib/SparseArraySpread.ts b/src/lualib/universal/SparseArraySpread.ts similarity index 100% rename from src/lualib/SparseArraySpread.ts rename to src/lualib/universal/SparseArraySpread.ts diff --git a/src/lualib/Unpack.ts b/src/lualib/universal/Unpack.ts similarity index 100% rename from src/lualib/Unpack.ts rename to src/lualib/universal/Unpack.ts From c18a1fc068778cb02f9eda5c033d76a7c32d94f5 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Tue, 30 Aug 2022 19:49:30 +0000 Subject: [PATCH 50/69] chore: add tsconfig to eslint --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 5b8fe5f4a..632294177 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,6 +5,7 @@ module.exports = { project: [ "test/tsconfig.json", "src/lualib/tsconfig.json", + "src/lualib/tsconfig.lua50.json", "benchmark/tsconfig.json", "language-extensions/tsconfig.json", ], From e5e9723c4e0de341c67a004f2ab43be7f223b613 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Tue, 30 Aug 2022 19:55:40 +0000 Subject: [PATCH 51/69] refactor(lualib): reorganize dist/lualib directories to better support future targets --- src/LuaLib.ts | 4 ++-- src/lualib/tsconfig.json | 2 +- src/lualib/tsconfig.lua50.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 4ce7d2baa..0a9b9f2e9 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -112,8 +112,8 @@ export interface LuaLibFeatureInfo { export type LuaLibModulesInfo = Record; export function resolveLuaLibDir(luaTarget: LuaTarget) { - const luaLibDir = luaTarget === LuaTarget.Lua50 ? "lualib-lua50" : "lualib"; - return path.resolve(__dirname, path.join("..", "dist", luaLibDir)); + const luaLibDir = luaTarget === LuaTarget.Lua50 ? "5.0" : "universal"; + return path.resolve(__dirname, path.join("..", "dist", "lualib", luaLibDir)); } export const luaLibModulesInfoFileName = "lualib_module_info.json"; diff --git a/src/lualib/tsconfig.json b/src/lualib/tsconfig.json index ef50543c6..de9c1fa7f 100644 --- a/src/lualib/tsconfig.json +++ b/src/lualib/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "outDir": "../../dist/lualib", + "outDir": "../../dist/lualib/universal", "target": "esnext", "lib": ["esnext"], "types": ["lua-types/5.4"], diff --git a/src/lualib/tsconfig.lua50.json b/src/lualib/tsconfig.lua50.json index b49ea6daa..ecf9409e1 100644 --- a/src/lualib/tsconfig.lua50.json +++ b/src/lualib/tsconfig.lua50.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "outDir": "../../dist/lualib-lua50", + "outDir": "../../dist/lualib/5.0", "target": "esnext", "lib": ["esnext"], "types": ["lua-types/5.0"], From e0d6206ba44de81a8455103999b55236dac30371 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Wed, 31 Aug 2022 06:13:44 +0000 Subject: [PATCH 52/69] fix(lualib): cache should account for multiple possible versions --- src/LuaLib.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 0a9b9f2e9..004b89c23 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -214,17 +214,17 @@ export function loadImportedLualibFeatures( return statements; } -let luaLibBundleContent: string; +const luaLibBundleContent = new Map(); export function getLuaLibBundle(luaTarget: LuaTarget, emitHost: EmitHost): string { - if (luaLibBundleContent === undefined) { + if (!luaLibBundleContent.has(luaTarget)) { const lualibPath = path.join(resolveLuaLibDir(luaTarget), "lualib_bundle.lua"); const result = emitHost.readFile(lualibPath); if (result !== undefined) { - luaLibBundleContent = result; + luaLibBundleContent.set(luaTarget, result); } else { throw new Error(`Could not load lualib bundle from '${lualibPath}'`); } } - return luaLibBundleContent; + return luaLibBundleContent.get(luaTarget) as string; } From 42f6de00f60aa70b06f3661d6343e485756e5c44 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Wed, 31 Aug 2022 06:14:47 +0000 Subject: [PATCH 53/69] Revert "test: use inline lualib imports to get around problems with preloading the entire bundle" This reverts commit 8d9ce98446cde7dc38e8fa3b988968c4dfb83bd8. --- test/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/util.ts b/test/util.ts index 0595224d4..8650f88ca 100644 --- a/test/util.ts +++ b/test/util.ts @@ -68,7 +68,7 @@ export function testEachVersion( const testName = name === undefined ? version : `${name} [${version}]`; defineTest(testName, () => { const builder = common(); - builder.setOptions({ luaTarget: version, luaLibImport: tstl.LuaLibImportKind.Inline }); + builder.setOptions({ luaTarget: version }); if (typeof specialBuilder === "function") { specialBuilder(builder); } From 9b3a223487c8ae9cc32f9bb93811b5b470ae088a Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Wed, 31 Aug 2022 07:08:37 +0000 Subject: [PATCH 54/69] chore: adjust jest and npm configs for new lualib structure --- jest.config.js | 1 - package.json | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/jest.config.js b/jest.config.js index 44b6bb379..2d390c93d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,7 +6,6 @@ module.exports = { collectCoverageFrom: [ "/src/**/*", "!/src/lualib/**/*", - "!/src/lualib-lua50/**/*", // https://github.com/facebook/jest/issues/5274 "!/src/tstl.ts", ], diff --git a/package.json b/package.json index 0d40231c7..f3e34ac90 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,7 @@ "dist/**/*.js", "dist/**/*.lua", "dist/**/*.ts", - "dist/lualib/*.json", - "dist/lualib-lua50/*.json", + "dist/lualib/**/*.json", "language-extensions/**/*.ts" ], "main": "dist/index.js", From 0953f2633293c0f6693c5bafab14106e0ba3f592 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Wed, 31 Aug 2022 16:36:07 +0000 Subject: [PATCH 55/69] refactor: cache lualib by file path so the universal target isn't cached several times --- src/LuaLib.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 004b89c23..17ebaaa6b 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -214,17 +214,17 @@ export function loadImportedLualibFeatures( return statements; } -const luaLibBundleContent = new Map(); +const luaLibBundleContent = new Map(); export function getLuaLibBundle(luaTarget: LuaTarget, emitHost: EmitHost): string { - if (!luaLibBundleContent.has(luaTarget)) { - const lualibPath = path.join(resolveLuaLibDir(luaTarget), "lualib_bundle.lua"); + const lualibPath = path.join(resolveLuaLibDir(luaTarget), "lualib_bundle.lua"); + if (!luaLibBundleContent.has(lualibPath)) { const result = emitHost.readFile(lualibPath); if (result !== undefined) { - luaLibBundleContent.set(luaTarget, result); + luaLibBundleContent.set(lualibPath, result); } else { throw new Error(`Could not load lualib bundle from '${lualibPath}'`); } } - return luaLibBundleContent.get(luaTarget) as string; + return luaLibBundleContent.get(lualibPath) as string; } From 2dc07a824c6424f25fd74529343f212cb16b5efc Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 1 Sep 2022 02:11:30 +0000 Subject: [PATCH 56/69] chore: bump lua-types --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d9acdf9b..16cf5e546 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "javascript-stringify": "^2.0.1", "jest": "^27.3.0", "jest-circus": "^27.3.0", - "lua-types": "^2.11.0", + "lua-types": "^2.12.2", "lua-wasm-bindings": "^0.3.0", "prettier": "^2.3.2", "ts-jest": "^27.1.3", @@ -4809,9 +4809,9 @@ } }, "node_modules/lua-types": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/lua-types/-/lua-types-2.12.0.tgz", - "integrity": "sha512-s96z1Zcz3KRXfWyNFM3KP2zpNYTfZwgHo8X/yFXveyS8Dux+cUuyT+zA5tFZbmGh8VViwNs9bFbzNevAR/18uw==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/lua-types/-/lua-types-2.12.2.tgz", + "integrity": "sha512-E9TfcaJ01YW6XrnXhdw9dvO495wkuXRfFJsWNfj/ZTvWrbloWQ0eNnGUkQ8OW/jaJCczJXuT9tW7wdtqL0sc+A==", "dev": true, "peerDependencies": { "typescript-to-lua": "^1.0.0" @@ -9990,9 +9990,9 @@ } }, "lua-types": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/lua-types/-/lua-types-2.12.0.tgz", - "integrity": "sha512-s96z1Zcz3KRXfWyNFM3KP2zpNYTfZwgHo8X/yFXveyS8Dux+cUuyT+zA5tFZbmGh8VViwNs9bFbzNevAR/18uw==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/lua-types/-/lua-types-2.12.2.tgz", + "integrity": "sha512-E9TfcaJ01YW6XrnXhdw9dvO495wkuXRfFJsWNfj/ZTvWrbloWQ0eNnGUkQ8OW/jaJCczJXuT9tW7wdtqL0sc+A==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index f3e34ac90..24207e1e2 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "javascript-stringify": "^2.0.1", "jest": "^27.3.0", "jest-circus": "^27.3.0", - "lua-types": "^2.11.0", + "lua-types": "^2.12.2", "lua-wasm-bindings": "^0.3.0", "prettier": "^2.3.2", "ts-jest": "^27.1.3", From 471554074a6defbb96fb27011a9c28e508e34e34 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 1 Sep 2022 04:44:50 +0000 Subject: [PATCH 57/69] refactor: deduplicate require boilerplate --- src/transpilation/bundle.ts | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/src/transpilation/bundle.ts b/src/transpilation/bundle.ts index d109fe3d0..c0b21b7d2 100644 --- a/src/transpilation/bundle.ts +++ b/src/transpilation/bundle.ts @@ -13,8 +13,11 @@ const createModulePath = (pathToResolve: string, program: ts.Program) => // Override `require` to read from ____modules table. function requireOverride(options: CompilerOptions) { - if (options.luaTarget === LuaTarget.Lua50) { - return ` + const runModule = + options.luaTarget === LuaTarget.Lua50 + ? "(table.getn(arg) > 0) and module(unpack(arg)) or module(file)" + : '(select("#", ...) > 0) and module(...) or module(file)'; + return ` local ____modules = {} local ____moduleCache = {} local ____originalRequire = require @@ -24,7 +27,7 @@ local function require(file, ...) end if ____modules[file] then local module = ____modules[file] - ____moduleCache[file] = { value = (table.getn(arg) > 0) and module(unpack(arg)) or module(file) } + ____moduleCache[file] = { value = ${runModule} } return ____moduleCache[file].value else if ____originalRequire then @@ -35,29 +38,6 @@ local function require(file, ...) end end `; - } else { - return ` -local ____modules = {} -local ____moduleCache = {} -local ____originalRequire = require -local function require(file, ...) - if ____moduleCache[file] then - return ____moduleCache[file].value - end - if ____modules[file] then - local module = ____modules[file] - ____moduleCache[file] = { value = (select("#", ...) > 0) and module(...) or module(file) } - return ____moduleCache[file].value - else - if ____originalRequire then - return ____originalRequire(file) - else - error("module '" .. file .. "' not found") - end - end -end -`; - } } export const sourceMapTracebackBundlePlaceholder = "{#SourceMapTracebackBundle}"; From 3bc745ced91aab80034f6b33d7b2f88def2be213 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Thu, 1 Sep 2022 04:59:14 +0000 Subject: [PATCH 58/69] refactor: simplify universal definition of TS_Match --- src/lualib/universal/Match.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lualib/universal/Match.ts b/src/lualib/universal/Match.ts index ebf72d154..464e985db 100644 --- a/src/lualib/universal/Match.ts +++ b/src/lualib/universal/Match.ts @@ -1,5 +1,3 @@ /** @noSelfInFile */ -export function __TS__Match(s: string, pattern: string, init?: number): LuaMultiReturn { - return string.match(s, pattern, init); -} +export const __TS__Match = string.match; From 236bd774ece164145eeb334143f2ade012e91ad4 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 2 Sep 2022 09:03:32 +0000 Subject: [PATCH 59/69] style: rename TS_Modulo -> TS_Modulo50 --- src/LuaLib.ts | 2 +- src/lualib/Modulo.ts | 5 ----- src/lualib/Modulo50.ts | 5 +++++ src/transformation/visitors/binary-expression/index.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 src/lualib/Modulo.ts create mode 100644 src/lualib/Modulo50.ts diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 17ebaaa6b..ffdc2e652 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -51,7 +51,7 @@ export enum LuaLibFeature { Match = "Match", MathAtan2 = "MathAtan2", MathSign = "MathSign", - Modulo = "Modulo", + Modulo50 = "Modulo50", New = "New", Number = "Number", NumberIsFinite = "NumberIsFinite", diff --git a/src/lualib/Modulo.ts b/src/lualib/Modulo.ts deleted file mode 100644 index b2f3c0fe8..000000000 --- a/src/lualib/Modulo.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** @noSelfInFile */ - -export function __TS__Modulo(a: number, b: number): number { - return a - Math.floor(a / b) * b; -} diff --git a/src/lualib/Modulo50.ts b/src/lualib/Modulo50.ts new file mode 100644 index 000000000..4b9729436 --- /dev/null +++ b/src/lualib/Modulo50.ts @@ -0,0 +1,5 @@ +/** @noSelfInFile */ + +export function __TS__Modulo50(a: number, b: number): number { + return a - Math.floor(a / b) * b; +} diff --git a/src/transformation/visitors/binary-expression/index.ts b/src/transformation/visitors/binary-expression/index.ts index 442dbbcc2..3b5521490 100644 --- a/src/transformation/visitors/binary-expression/index.ts +++ b/src/transformation/visitors/binary-expression/index.ts @@ -70,7 +70,7 @@ function transformBinaryOperationWithNoPrecedingStatements( } if (operator === ts.SyntaxKind.PercentToken && context.luaTarget === LuaTarget.Lua50) { - return transformLuaLibFunction(context, LuaLibFeature.Modulo, node, left, right); + return transformLuaLibFunction(context, LuaLibFeature.Modulo50, node, left, right); } let luaOperator = simpleOperatorsToLua[operator]; From 9820e42473c8b8617d62a1a5887fe4a54359423c Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 2 Sep 2022 09:13:52 +0000 Subject: [PATCH 60/69] refactor(lualib): use unpack helper to avoid code duplication in Generator.ts --- src/lualib/{5.0 => }/Generator.ts | 3 ++- src/lualib/universal/Generator.ts | 28 ---------------------------- 2 files changed, 2 insertions(+), 29 deletions(-) rename src/lualib/{5.0 => }/Generator.ts (87%) delete mode 100644 src/lualib/universal/Generator.ts diff --git a/src/lualib/5.0/Generator.ts b/src/lualib/Generator.ts similarity index 87% rename from src/lualib/5.0/Generator.ts rename to src/lualib/Generator.ts index 082266119..c17aab8c7 100644 --- a/src/lualib/5.0/Generator.ts +++ b/src/lualib/Generator.ts @@ -1,5 +1,6 @@ import { __TS__CountVarargs } from "./CountVarargs"; import { GeneratorIterator } from "./GeneratorIterator"; +import { __TS__Unpack } from "./Unpack"; function generatorIterator(this: GeneratorIterator) { return this; @@ -20,7 +21,7 @@ export function __TS__Generator(this: void, fn: (this: void, ...args: any[]) => const argsLength = __TS__CountVarargs(...args); return { // Using explicit this there, since we don't pass arguments after the first nil and context is likely to be nil - ____coroutine: coroutine.create(() => fn(...unpack(args, 1, argsLength))), + ____coroutine: coroutine.create(() => fn(...__TS__Unpack(args, 1, argsLength))), [Symbol.iterator]: generatorIterator, next: generatorNext, }; diff --git a/src/lualib/universal/Generator.ts b/src/lualib/universal/Generator.ts deleted file mode 100644 index d64bf781a..000000000 --- a/src/lualib/universal/Generator.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { __TS__CountVarargs } from "./CountVarargs"; -import { GeneratorIterator } from "./GeneratorIterator"; - -function generatorIterator(this: GeneratorIterator) { - return this; -} - -function generatorNext(this: GeneratorIterator, ...args: any[]) { - const co = this.____coroutine; - if (coroutine.status(co) === "dead") return { done: true }; - - const [status, value] = coroutine.resume(co, ...args); - if (!status) throw value; - - return { value, done: coroutine.status(co) === "dead" }; -} - -export function __TS__Generator(this: void, fn: (this: void, ...args: any[]) => any) { - return function (this: void, ...args: any[]): GeneratorIterator { - const argsLength = __TS__CountVarargs(...args); - return { - // Using explicit this there, since we don't pass arguments after the first nil and context is likely to be nil - ____coroutine: coroutine.create(() => fn(...(unpack ?? table.unpack)(args, 1, argsLength))), - [Symbol.iterator]: generatorIterator, - next: generatorNext, - }; - }; -} From 581485960ed7c053ba1e2e20a68f4dacb5726629 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 2 Sep 2022 09:21:20 +0000 Subject: [PATCH 61/69] refactor(lualib): eliminate duplication in SourceMapTraceBack.ts --- src/lualib/{5.0 => }/SourceMapTraceBack.ts | 5 +- src/lualib/universal/SourceMapTraceBack.ts | 69 ---------------------- 2 files changed, 4 insertions(+), 70 deletions(-) rename src/lualib/{5.0 => }/SourceMapTraceBack.ts (93%) delete mode 100644 src/lualib/universal/SourceMapTraceBack.ts diff --git a/src/lualib/5.0/SourceMapTraceBack.ts b/src/lualib/SourceMapTraceBack.ts similarity index 93% rename from src/lualib/5.0/SourceMapTraceBack.ts rename to src/lualib/SourceMapTraceBack.ts index 25c23e9c6..ba5908255 100644 --- a/src/lualib/5.0/SourceMapTraceBack.ts +++ b/src/lualib/SourceMapTraceBack.ts @@ -20,8 +20,11 @@ export function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap let trace: string; if (thread === undefined && message === undefined && level === undefined) { trace = originalTraceback(); - } else { + } else if (_VERSION.includes("Lua 5.0")) { trace = originalTraceback(`[Level ${level}] ${message}`); + } else { + // @ts-ignore Fails when compiled with Lua 5.0 types + trace = originalTraceback(thread, message, level); } if (typeof trace !== "string") { diff --git a/src/lualib/universal/SourceMapTraceBack.ts b/src/lualib/universal/SourceMapTraceBack.ts deleted file mode 100644 index 4022a7e00..000000000 --- a/src/lualib/universal/SourceMapTraceBack.ts +++ /dev/null @@ -1,69 +0,0 @@ -// TODO: In the future, change this to __TS__RegisterFileInfo and provide tstl interface to -// get some metadata about transpilation. - -import { __TS__Match } from "./Match"; - -interface SourceMap { - [line: number]: number | { line: number; file: string }; -} - -declare function __TS__originalTraceback(this: void, thread?: LuaThread, message?: string, level?: number); - -export function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap: SourceMap): void { - globalThis.__TS__sourcemap = globalThis.__TS__sourcemap || {}; - globalThis.__TS__sourcemap[fileName] = sourceMap; - - if (globalThis.__TS__originalTraceback === undefined) { - const originalTraceback = debug.traceback; - globalThis.__TS__originalTraceback = originalTraceback; - debug.traceback = ((thread, message, level) => { - let trace: string; - if (thread === undefined && message === undefined && level === undefined) { - trace = originalTraceback(); - } else { - trace = originalTraceback(thread, message, level); - } - - if (typeof trace !== "string") { - return trace; - } - - const replacer = (file: string, srcFile: string, line: string) => { - const fileSourceMap: SourceMap = globalThis.__TS__sourcemap[file]; - if (fileSourceMap && fileSourceMap[line]) { - const data = fileSourceMap[line]; - if (typeof data === "number") { - return `${srcFile}:${data}`; - } - - return `${data.file}:${data.line}`; - } - - return `${file}:${line}`; - }; - - let [result] = string.gsub(trace, "(%S+)%.lua:(%d+)", (file, line) => - replacer(`${file}.lua`, `${file}.ts`, line) - ); - - const stringReplacer = (file: string, line: string) => { - const fileSourceMap: SourceMap = globalThis.__TS__sourcemap[file]; - if (fileSourceMap && fileSourceMap[line]) { - const chunkName = __TS__Match(file, '%[string "([^"]+)"%]')[0]; - const [sourceName] = string.gsub(chunkName, ".lua$", ".ts"); - const data = fileSourceMap[line]; - if (typeof data === "number") { - return `${sourceName}:${data}`; - } - - return `${data.file}:${data.line}`; - } - - return `${file}:${line}`; - }; - [result] = string.gsub(result, '(%[string "[^"]+"%]):(%d+)', (file, line) => stringReplacer(file, line)); - - return result; - }) as typeof debug.traceback; - } -} From 96c6f48fb5c3391b4ea322052f543c478ebf0ee1 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 2 Sep 2022 09:26:06 +0000 Subject: [PATCH 62/69] chore(lualib): fix comment in CountVarargs.ts --- src/lualib/universal/CountVarargs.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lualib/universal/CountVarargs.ts b/src/lualib/universal/CountVarargs.ts index 2441b0447..dc6107bb5 100644 --- a/src/lualib/universal/CountVarargs.ts +++ b/src/lualib/universal/CountVarargs.ts @@ -1,7 +1,6 @@ /** @noSelfInFile */ export function __TS__CountVarargs(...args: T[]): number { - // It is important that the select() branch come first as we need vararg - // optimization for this call. + // Note that we need vararg optimization for this call. return select("#", ...args); } From cd9b219cd060076ee6e5669544a46d13ec398bc2 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 2 Sep 2022 09:33:03 +0000 Subject: [PATCH 63/69] refactor(lualib): eliminate duplication in Error.ts --- src/lualib/5.0/Error.ts | 71 ----------------------------- src/lualib/{universal => }/Error.ts | 11 ++++- 2 files changed, 9 insertions(+), 73 deletions(-) delete mode 100644 src/lualib/5.0/Error.ts rename src/lualib/{universal => }/Error.ts (83%) diff --git a/src/lualib/5.0/Error.ts b/src/lualib/5.0/Error.ts deleted file mode 100644 index 29a478a12..000000000 --- a/src/lualib/5.0/Error.ts +++ /dev/null @@ -1,71 +0,0 @@ -interface ErrorType { - name: string; - new (...args: any[]): Error; -} - -function getErrorStack(constructor: () => any): string { - let level = 1; - while (true) { - const info = debug.getinfo(level, "f"); - level += 1; - if (!info) { - // constructor is not in call stack - level = 1; - break; - } else if (info.func === constructor) { - break; - } - } - - return debug.traceback(`[Level ${level}]`); -} - -function wrapErrorToString(getDescription: (this: T) => string): (this: T) => string { - return function (this: Error): string { - const description = getDescription.call(this); - return description; - }; -} - -function initErrorClass(Type: ErrorType, name: string): any { - Type.name = name; - return setmetatable(Type, { - __call: (_self: any, message: string) => new Type(message), - }); -} - -export const Error: ErrorConstructor = initErrorClass( - class implements Error { - public name = "Error"; - public stack: string; - - constructor(public message = "") { - this.stack = getErrorStack((this.constructor as any).new); - const metatable = getmetatable(this); - if (!metatable.__errorToStringPatched) { - metatable.__errorToStringPatched = true; - metatable.__tostring = wrapErrorToString(metatable.__tostring); - } - } - - public toString(): string { - return this.message !== "" ? `${this.name}: ${this.message}` : this.name; - } - }, - "Error" -); - -function createErrorClass(name: string) { - return initErrorClass( - class extends Error { - public name = name; - }, - name - ); -} - -export const RangeError = createErrorClass("RangeError"); -export const ReferenceError = createErrorClass("ReferenceError"); -export const SyntaxError = createErrorClass("SyntaxError"); -export const TypeError = createErrorClass("TypeError"); -export const URIError = createErrorClass("URIError"); diff --git a/src/lualib/universal/Error.ts b/src/lualib/Error.ts similarity index 83% rename from src/lualib/universal/Error.ts rename to src/lualib/Error.ts index 2ebb2aaaa..ddad7d81f 100644 --- a/src/lualib/universal/Error.ts +++ b/src/lualib/Error.ts @@ -17,14 +17,21 @@ function getErrorStack(constructor: () => any): string { } } - return debug.traceback(undefined, level); + if (_VERSION.includes("Lua 5.0")) { + return debug.traceback(`[Level ${level}]`); + } else { + // @ts-ignore Fails when compiled with Lua 5.0 types + return debug.traceback(undefined, level); + } } function wrapErrorToString(getDescription: (this: T) => string): (this: T) => string { return function (this: Error): string { const description = getDescription.call(this); const caller = debug.getinfo(3, "f"); - if (_VERSION === "Lua 5.1" || (caller && caller.func !== error)) { + // @ts-ignore Fails when compiled with Lua 5.0 types + const isClassicLua = _VERSION.includes("Lua 5.0") || _VERSION === "Lua 5.1"; + if (isClassicLua || (caller && caller.func !== error)) { return description; } else { return `${description}\n${this.stack}`; From 023b5e41567e77b647739da27beed3ebd45fcf25 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 2 Sep 2022 10:11:21 +0000 Subject: [PATCH 64/69] fix(lualib): modules info should be cached per-target --- src/LuaLib.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LuaLib.ts b/src/LuaLib.ts index ffdc2e652..295740007 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -117,18 +117,18 @@ export function resolveLuaLibDir(luaTarget: LuaTarget) { } export const luaLibModulesInfoFileName = "lualib_module_info.json"; -let luaLibModulesInfo: LuaLibModulesInfo | undefined; +const luaLibModulesInfo = new Map(); export function getLuaLibModulesInfo(luaTarget: LuaTarget, emitHost: EmitHost): LuaLibModulesInfo { - if (luaLibModulesInfo === undefined) { + if (!luaLibModulesInfo.has(luaTarget)) { const lualibPath = path.join(resolveLuaLibDir(luaTarget), luaLibModulesInfoFileName); const result = emitHost.readFile(lualibPath); if (result !== undefined) { - luaLibModulesInfo = JSON.parse(result) as LuaLibModulesInfo; + luaLibModulesInfo.set(luaTarget, JSON.parse(result) as LuaLibModulesInfo); } else { throw new Error(`Could not load lualib dependencies from '${lualibPath}'`); } } - return luaLibModulesInfo; + return luaLibModulesInfo.get(luaTarget) as LuaLibModulesInfo; } export function readLuaLibFeature(feature: LuaLibFeature, luaTarget: LuaTarget, emitHost: EmitHost): string { From 91376835ee4d10043d3ad9acfa3ba380b70b128d Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 2 Sep 2022 18:06:40 +0000 Subject: [PATCH 65/69] refactor(lualib): eliminate duplication in NumbertToString.ts --- src/LuaLib.ts | 1 + src/lualib/5.0/MathModf.ts | 6 +++ src/lualib/5.0/NumberToString.ts | 52 -------------------- src/lualib/{universal => }/NumberToString.ts | 4 +- src/lualib/universal/MathModf.ts | 1 + 5 files changed, 11 insertions(+), 53 deletions(-) create mode 100644 src/lualib/5.0/MathModf.ts delete mode 100644 src/lualib/5.0/NumberToString.ts rename src/lualib/{universal => }/NumberToString.ts (92%) create mode 100644 src/lualib/universal/MathModf.ts diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 295740007..cbb944e09 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -50,6 +50,7 @@ export enum LuaLibFeature { Map = "Map", Match = "Match", MathAtan2 = "MathAtan2", + MathModf = "MathModf", MathSign = "MathSign", Modulo50 = "Modulo50", New = "New", diff --git a/src/lualib/5.0/MathModf.ts b/src/lualib/5.0/MathModf.ts new file mode 100644 index 000000000..ec816ebe0 --- /dev/null +++ b/src/lualib/5.0/MathModf.ts @@ -0,0 +1,6 @@ +/** @noSelfInFile */ + +export function __TS__MathModf(x: number): LuaMultiReturn<[number, number]> { + const integral = x > 0 ? Math.floor(x) : Math.ceil(x); + return $multi(integral, x - integral); +} diff --git a/src/lualib/5.0/NumberToString.ts b/src/lualib/5.0/NumberToString.ts deleted file mode 100644 index a265bc3d9..000000000 --- a/src/lualib/5.0/NumberToString.ts +++ /dev/null @@ -1,52 +0,0 @@ -const radixChars = "0123456789abcdefghijklmnopqrstuvwxyz"; - -function modf(this: void, x: number): LuaMultiReturn<[number, number]> { - const integral = x > 0 ? Math.floor(x) : Math.ceil(x); - return $multi(integral, x - integral); -} - -// https://www.ecma-international.org/ecma-262/10.0/index.html#sec-number.prototype.tostring -export function __TS__NumberToString(this: number, radix?: number): string { - if (radix === undefined || radix === 10 || this === Infinity || this === -Infinity || this !== this) { - return this.toString(); - } - - radix = Math.floor(radix); - if (radix < 2 || radix > 36) { - throw "toString() radix argument must be between 2 and 36"; - } - - let [integer, fraction] = modf(Math.abs(this)); - - let result = ""; - if (radix === 8) { - result = string.format("%o", integer); - } else if (radix === 16) { - result = string.format("%x", integer); - } else { - do { - result = radixChars[integer % radix] + result; - integer = Math.floor(integer / radix); - } while (integer !== 0); - } - - // https://github.com/v8/v8/blob/f78e8d43c224847fa56b3220a90be250fc0f0d6e/src/numbers/conversions.cc#L1221 - if (fraction !== 0) { - result += "."; - let delta = 1e-16; - do { - fraction *= radix; - delta *= radix; - const digit = Math.floor(fraction); - result += radixChars[digit]; - fraction -= digit; - // TODO: Round to even - } while (fraction >= delta); - } - - if (this < 0) { - result = "-" + result; - } - - return result; -} diff --git a/src/lualib/universal/NumberToString.ts b/src/lualib/NumberToString.ts similarity index 92% rename from src/lualib/universal/NumberToString.ts rename to src/lualib/NumberToString.ts index 1e1a1ad03..6cdbaadc3 100644 --- a/src/lualib/universal/NumberToString.ts +++ b/src/lualib/NumberToString.ts @@ -1,3 +1,5 @@ +import { __TS__MathModf } from "./MathModf"; + const radixChars = "0123456789abcdefghijklmnopqrstuvwxyz"; // https://www.ecma-international.org/ecma-262/10.0/index.html#sec-number.prototype.tostring @@ -11,7 +13,7 @@ export function __TS__NumberToString(this: number, radix?: number): string { throw "toString() radix argument must be between 2 and 36"; } - let [integer, fraction] = math.modf(Math.abs(this)); + let [integer, fraction] = __TS__MathModf(Math.abs(this)); let result = ""; if (radix === 8) { diff --git a/src/lualib/universal/MathModf.ts b/src/lualib/universal/MathModf.ts new file mode 100644 index 000000000..139ae8f6a --- /dev/null +++ b/src/lualib/universal/MathModf.ts @@ -0,0 +1 @@ +export const __TS__MathModf = math.modf; From 4713b69aef3ebbfb21231ff54fa793a67c9f9b88 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 2 Sep 2022 18:13:17 +0000 Subject: [PATCH 66/69] refactor(lualib): modules info can also be cached by file path to avoid duplication --- src/LuaLib.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/LuaLib.ts b/src/LuaLib.ts index cbb944e09..2ab5d33ba 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -118,18 +118,18 @@ export function resolveLuaLibDir(luaTarget: LuaTarget) { } export const luaLibModulesInfoFileName = "lualib_module_info.json"; -const luaLibModulesInfo = new Map(); +const luaLibModulesInfo = new Map(); export function getLuaLibModulesInfo(luaTarget: LuaTarget, emitHost: EmitHost): LuaLibModulesInfo { - if (!luaLibModulesInfo.has(luaTarget)) { - const lualibPath = path.join(resolveLuaLibDir(luaTarget), luaLibModulesInfoFileName); + const lualibPath = path.join(resolveLuaLibDir(luaTarget), luaLibModulesInfoFileName); + if (!luaLibModulesInfo.has(lualibPath)) { const result = emitHost.readFile(lualibPath); if (result !== undefined) { - luaLibModulesInfo.set(luaTarget, JSON.parse(result) as LuaLibModulesInfo); + luaLibModulesInfo.set(lualibPath, JSON.parse(result) as LuaLibModulesInfo); } else { throw new Error(`Could not load lualib dependencies from '${lualibPath}'`); } } - return luaLibModulesInfo.get(luaTarget) as LuaLibModulesInfo; + return luaLibModulesInfo.get(lualibPath) as LuaLibModulesInfo; } export function readLuaLibFeature(feature: LuaLibFeature, luaTarget: LuaTarget, emitHost: EmitHost): string { From 92a36a2078fd787f75b229d02f679281ff1ad433 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 2 Sep 2022 18:20:58 +0000 Subject: [PATCH 67/69] style: for locating lualib features, it makes more sense to default to the universal target --- src/LuaPrinter.ts | 2 +- src/lualib-build/plugin.ts | 2 +- src/transpilation/transpiler.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 044b55207..ab2d373da 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -233,7 +233,7 @@ export class LuaPrinter { sourceChunks.push(tstlHeader); } - const luaTarget = this.options.luaTarget ?? LuaTarget.Lua54; + const luaTarget = this.options.luaTarget ?? LuaTarget.Universal; const luaLibImport = this.options.luaLibImport ?? LuaLibImportKind.Require; if (luaLibImport === LuaLibImportKind.Require && file.luaLibFeatures.size > 0) { // Import lualib features diff --git a/src/lualib-build/plugin.ts b/src/lualib-build/plugin.ts index 5c65ad546..4d7722d12 100644 --- a/src/lualib-build/plugin.ts +++ b/src/lualib-build/plugin.ts @@ -57,7 +57,7 @@ class LuaLibPlugin implements tstl.Plugin { // Figure out the order required in the bundle by recursively resolving all dependency features const allFeatures = Object.values(LuaLibFeature) as LuaLibFeature[]; - const luaTarget = options.luaTarget ?? tstl.LuaTarget.Lua54; + const luaTarget = options.luaTarget ?? tstl.LuaTarget.Universal; const orderedFeatures = resolveRecursiveLualibFeatures(allFeatures, luaTarget, emitHost, luaLibModuleInfo); // Concatenate lualib files into bundle with exports table and add lualib_bundle.lua to results diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index 2f69f3e21..2f05aae64 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -126,7 +126,7 @@ export class Transpiler { // Add lualib bundle to source dir 'virtually', will be moved to correct output dir in emitPlan const fileName = normalizeSlashes(path.resolve(getSourceDir(program), "lualib_bundle.lua")); - const luaTarget = options.luaTarget ?? LuaTarget.Lua54; + const luaTarget = options.luaTarget ?? LuaTarget.Universal; resolutionResult.resolvedFiles.unshift({ fileName, code: getLuaLibBundle(luaTarget, this.emitHost) }); } From d7014a4a6221d947cbcd86ed4cb691b887414a8c Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 2 Sep 2022 18:26:16 +0000 Subject: [PATCH 68/69] style: delete unnecessary noselfinfile --- src/lualib/universal/Match.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lualib/universal/Match.ts b/src/lualib/universal/Match.ts index 464e985db..de9420b59 100644 --- a/src/lualib/universal/Match.ts +++ b/src/lualib/universal/Match.ts @@ -1,3 +1 @@ -/** @noSelfInFile */ - export const __TS__Match = string.match; From 7d90ef7e2258a9a713c13552b112318be0daab8b Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 2 Sep 2022 20:20:07 +0000 Subject: [PATCH 69/69] chore: add 5.0 target to tsconfig schema --- tsconfig-schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig-schema.json b/tsconfig-schema.json index 5a3543a03..0d43fe74c 100644 --- a/tsconfig-schema.json +++ b/tsconfig-schema.json @@ -50,7 +50,7 @@ "description": "Specifies the Lua version you want to generate code for.", "type": "string", "default": "universal", - "enum": ["universal", "5.1", "5.2", "5.3", "JIT"] + "enum": ["5.0", "universal", "5.1", "5.2", "5.3", "JIT"] }, "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.",