import * as tstl from "../../src";
import { LuaTarget } from "../../src";
import { forbiddenForIn } from "../../src/transformation/utils/diagnostics";
import * as util from "../util";
test("while", () => {
util.testFunction`
let arrTest = [0, 1, 2, 3];
let i = 0;
while (i < arrTest.length) {
arrTest[i] = arrTest[i] + 1;
i++;
}
return arrTest;
`.expectToMatchJsResult();
});
util.testEachVersion(
"while with continue",
() => util.testFunction`
let arrTest = [0, 1, 2, 3, 4];
let i = 0;
while (i < arrTest.length) {
if (i % 2 == 0) {
i++;
continue;
}
let j = 2;
while (j > 0) {
if (j == 2) {
j--
continue;
}
arrTest[i] = j;
j--;
}
i++;
}
return arrTest;
`,
util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult())
);
util.testEachVersion(
"dowhile with continue",
() => util.testFunction`
let arrTest = [0, 1, 2, 3, 4];
let i = 0;
do {
if (i % 2 == 0) {
i++;
continue;
}
let j = 2;
do {
if (j == 2) {
j--
continue;
}
arrTest[i] = j;
j--;
} while (j > 0)
i++;
} while (i < arrTest.length)
return arrTest;
`,
util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult())
);
test("for", () => {
util.testFunction`
let arrTest = [0, 1, 2, 3];
for (let i = 0; i < arrTest.length; ++i) {
arrTest[i] = arrTest[i] + 1;
}
return arrTest;
`.expectToMatchJsResult();
});
test("for with expression", () => {
util.testFunction`
let arrTest = [0, 1, 2, 3];
let i: number;
for (i = 0 * 1; i < arrTest.length; ++i) {
arrTest[i] = arrTest[i] + 1;
}
return arrTest;
`.expectToMatchJsResult();
});
util.testEachVersion(
"for with continue",
() => util.testFunction`
let arrTest = [0, 1, 2, 3, 4];
for (let i = 0; i < arrTest.length; i++) {
if (i % 2 == 0) {
continue;
}
for (let j = 0; j < 2; j++) {
if (j == 1) {
continue;
}
arrTest[i] = j;
}
}
return arrTest;
`,
util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult())
);
test("forMirror", () => {
util.testFunction`
let arrTest = [0, 1, 2, 3];
for (let i = 0; arrTest.length > i; i++) {
arrTest[i] = arrTest[i] + 1;
}
return arrTest;
`.expectToMatchJsResult();
});
test("forBreak", () => {
util.testFunction`
let arrTest = [0, 1, 2, 3];
for (let i = 0; i < arrTest.length; ++i) {
break;
arrTest[i] = arrTest[i] + 1;
}
return arrTest;
`.expectToMatchJsResult();
});
test("forNoDeclarations", () => {
util.testFunction`
let arrTest = [0, 1, 2, 3];
let i = 0;
for (; i < arrTest.length; ++i) {
arrTest[i] = arrTest[i] + 1;
}
return arrTest;
`.expectToMatchJsResult();
});
test("forNoCondition", () => {
util.testFunction`
let arrTest = [0, 1, 2, 3];
let i = 0;
for (;; ++i) {
if (i >= arrTest.length) {
break;
}
arrTest[i] = arrTest[i] + 1;
}
return arrTest;
`.expectToMatchJsResult();
});
test("forNoPostExpression", () => {
util.testFunction`
let arrTest = [0, 1, 2, 3];
let i = 0;
for (;;) {
if (i >= arrTest.length) {
break;
}
arrTest[i] = arrTest[i] + 1;
i++;
}
return arrTest;
`.expectToMatchJsResult();
});
test.each([
{ inp: [0, 1, 2, 3], header: "let i = 0; i < arrTest.length; i++" },
{ inp: [0, 1, 2, 3], header: "let i = 0; i <= arrTest.length - 1; i++" },
{ inp: [0, 1, 2, 3], header: "let i = 0; arrTest.length > i; i++" },
{ inp: [0, 1, 2, 3], header: "let i = 0; arrTest.length - 1 >= i; i++" },
{ inp: [0, 1, 2, 3], header: "let i = 0; i < arrTest.length; i += 2" },
{ inp: [0, 1, 2, 3], header: "let i = arrTest.length - 1; i >= 0; i--" },
{ inp: [0, 1, 2, 3], header: "let i = arrTest.length - 1; i >= 0; i -= 2" },
{ inp: [0, 1, 2, 3], header: "let i = arrTest.length - 1; i > 0; i -= 2" },
])("forheader (%p)", ({ inp, header }) => {
util.testFunction`
let arrTest = ${util.formatCode(inp)};
for (${header}) {
arrTest[i] = arrTest[i] + 1;
}
return arrTest;
`.expectToMatchJsResult();
});
test("for scope", () => {
util.testFunction`
let i = 42;
for (let i = 0; i < 10; ++i) {}
return i;
`.expectToMatchJsResult();
});
test.each([
{
inp: { test1: 0, test2: 1, test3: 2 },
},
])("forin[Object] (%p)", ({ inp }) => {
util.testFunctionTemplate`
let objTest = ${inp};
for (let key in objTest) {
objTest[key] = objTest[key] + 1;
}
return objTest;
`.expectToMatchJsResult();
});
test("forin[Array]", () => {
util.testFunction`
const array = [];
for (const key in array) {}
`.expectDiagnosticsToMatchSnapshot([forbiddenForIn.code]);
});
const luaTargetsExceptJit = [
LuaTarget.Lua50,
LuaTarget.Lua51,
LuaTarget.Lua52,
LuaTarget.Lua53,
LuaTarget.Lua54,
LuaTarget.Universal,
];
test.each(
luaTargetsExceptJit.flatMap(target =>
[{ inp: { a: 0, b: 1, c: 2, d: 3, e: 4 } }].map(testCase => [target, testCase] as const)
)
)("forin with continue (%s %p)", (luaTarget, { inp }) => {
util.testFunctionTemplate`
let obj = ${inp};
for (let i in obj) {
if (obj[i] % 2 == 0) {
continue;
}
obj[i] = 0;
}
return obj;
`
.setOptions({ luaTarget })
.expectToMatchJsResult();
});
test.each([{ inp: [0, 1, 2] }])("forof (%p)", ({ inp }) => {
util.testFunctionTemplate`
let objTest = ${inp};
let arrResultTest = [];
for (let value of objTest) {
arrResultTest.push(value + 1)
}
return arrResultTest;
`.expectToMatchJsResult();
});
test("Tuple loop", () => {
util.testFunction`
const tuple: [number, number, number] = [3,5,1];
let count = 0;
for (const value of tuple) { count += value; }
return count;
`.expectToMatchJsResult();
});
test("forof existing variable", () => {
util.testFunction`
let objTest = [0, 1, 2];
let arrResultTest = [];
let value: number;
for (value of objTest) {
arrResultTest.push(value + 1)
}
return arrResultTest;
`.expectToMatchJsResult();
});
test("forof destructing", () => {
const input = [
[1, 2],
[2, 3],
[3, 4],
];
util.testFunction`
let objTest = ${util.formatCode(input)};
let arrResultTest = [];
for (let [a,b] of objTest) {
arrResultTest.push(a + b)
}
return arrResultTest;
`.expectToMatchJsResult();
});
test("forof destructing scope", () => {
util.testFunction`
let x = 7;
for (let [x] of [[1], [2], [3]]) {
x *= 2;
}
return x;
`.expectToMatchJsResult();
});
// This catches the case where x is falsely seen as globally scoped and the 'local' is stripped out
test("forof destructing scope (global)", () => {
util.testModule`
let x = 7;
for (let [x] of [[1], [2], [3]]) {
x *= 2;
}
if (x !== 7) throw x;
`.expectNoExecutionError();
});
test("forof nested destructing", () => {
util.testFunction`
const obj = { a: [3], b: [5] };
let result = 0;
for(const [k, [v]] of Object.entries(obj)){
result += v;
}
return result;
`.expectToMatchJsResult();
});
test("forof destructing with existing variables", () => {
const input = [
[1, 2],
[2, 3],
[3, 4],
];
util.testFunction`
let objTest = ${util.formatCode(input)};
let arrResultTest = [];
let a: number;
let b: number;
for ([a,b] of objTest) {
arrResultTest.push(a + b)
}
return arrResultTest;
`.expectToMatchJsResult();
});
util.testEachVersion(
"forof with continue",
() => util.testFunction`
let testArr = [0, 1, 2, 3, 4];
let a = 0;
for (let i of testArr) {
if (i % 2 == 0) {
a++;
continue;
}
for (let j of [0, 1]) {
if (j == 1) {
continue;
}
testArr[a] = j;
}
a++;
}
return testArr;
`,
util.expectEachVersionExceptJit(builder => builder.expectToMatchJsResult())
);
test("forof with iterator", () => {
util.testFunction`
const arr = ["a", "b", "c"];
function iter(): IterableIterator {
let i = 0;
return {
[Symbol.iterator]() { return this; },
next() { return {value: arr[i], done: i++ >= arr.length} },
}
}
let result = "";
for (let e of iter()) {
result += e;
}
return result;
`.expectToMatchJsResult();
});
test("forof with iterator and existing variable", () => {
util.testFunction`
const arr = ["a", "b", "c"];
function iter(): IterableIterator {
let i = 0;
return {
[Symbol.iterator]() { return this; },
next() { return {value: arr[i], done: i++ >= arr.length} },
}
}
let result = "";
let e: string;
for (e of iter()) {
result += e;
}
return result;
`.expectToMatchJsResult();
});
test("forof destructuring with iterator", () => {
util.testFunction`
const arr = ["a", "b", "c"];
function iter(): IterableIterator<[string, string]> {
let i = 0;
return {
[Symbol.iterator]() { return this; },
next() { return {value: [i.toString(), arr[i]], done: i++ >= arr.length} },
}
}
let result = "";
for (let [a, b] of iter()) {
result += a + b;
}
return result;
`.expectToMatchJsResult();
});
test("forof destructuring with iterator and existing variables", () => {
util.testFunction`
const arr = ["a", "b", "c"];
function iter(): IterableIterator<[string, string]> {
let i = 0;
return {
[Symbol.iterator]() { return this; },
next() { return {value: [i.toString(), arr[i]], done: i++ >= arr.length} },
}
}
let result = "";
let a: string;
let b: string;
for ([a, b] of iter()) {
result += a + b;
}
return result;
`.expectToMatchJsResult();
});
test("forof array which modifies length", () => {
util.testFunction`
const arr = ["a", "b", "c"];
let result = "";
for (const v of arr) {
if (v === "a") {
arr.push("d");
}
result += v;
}
return result;
`.expectToMatchJsResult();
});
test("forof nested destructuring", () => {
util.testFunction`
let result = 0;
for (const [[x]] of [[[1]]]) {
result = x;
}
return result;
`.expectToMatchJsResult();
});
test("forof with array typed as iterable", () => {
util.testFunction`
function foo(): Iterable {
return ["A", "B", "C"];
}
let result = "";
for (const x of foo()) {
result += x;
}
return result;
`.expectToMatchJsResult();
});
test.each(["", "abc", "a\0c"])("forof string (%p)", string => {
util.testFunctionTemplate`
const results: string[] = [];
for (const x of ${string}) {
results.push(x);
}
return results;
`.expectToMatchJsResult();
});
describe("for...of empty destructuring", () => {
const declareTests = (destructuringPrefix: string) => {
test("array", () => {
util.testFunction`
const arr = [["a"], ["b"], ["c"]];
let i = 0;
for (${destructuringPrefix}[] of arr) {
++i;
}
return i;
`.expectToMatchJsResult();
});
test("iterable", () => {
util.testFunction`
const iter: Iterable = [["a"], ["b"], ["c"]];
let i = 0;
for (${destructuringPrefix}[] of iter) {
++i;
}
return i;
`.expectToMatchJsResult();
});
};
describe("declaration", () => {
declareTests("const ");
});
describe("assignment", () => {
declareTests("");
});
});
for (const testCase of [
"while (false) { continue; }",
"do { continue; } while (false)",
"for (;;) { continue; }",
"for (const a in {}) { continue; }",
"for (const a of []) { continue; }",
]) {
const expectContinueVariable: util.TapCallback = builder =>
expect(builder.getMainLuaCodeChunk()).toMatch(/local __continue\d+/);
const expectContinueGotoLabel: util.TapCallback = builder =>
expect(builder.getMainLuaCodeChunk()).toMatch(/::__continue\d+::/);
const expectContinueStatement: util.TapCallback = builder =>
expect(builder.getMainLuaCodeChunk()).toMatch("continue;");
util.testEachVersion(`loop continue (${testCase})`, () => util.testModule(testCase), {
[tstl.LuaTarget.Universal]: expectContinueVariable,
[tstl.LuaTarget.Lua50]: expectContinueVariable,
[tstl.LuaTarget.Lua51]: expectContinueVariable,
[tstl.LuaTarget.Lua52]: expectContinueGotoLabel,
[tstl.LuaTarget.Lua53]: expectContinueGotoLabel,
[tstl.LuaTarget.Lua54]: expectContinueGotoLabel,
[tstl.LuaTarget.Lua55]: expectContinueGotoLabel,
[tstl.LuaTarget.LuaJIT]: expectContinueGotoLabel,
[tstl.LuaTarget.Luau]: () => expectContinueStatement,
});
}
// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1638
test.each([tstl.LuaTarget.Universal, tstl.LuaTarget.Lua50, tstl.LuaTarget.Lua51])(
"no unreachable code when using continue for target %s (#1638)",
target => {
util.testFunction`
let i = 0;
while(++i < 10) continue;
return i;
`
.setOptions({ luaTarget: target })
.expectToMatchJsResult();
}
);
test("do...while", () => {
util.testFunction`
let result = 0;
do {
++result;
} while (result < 2);
return result;
`.expectToMatchJsResult();
});
test("do...while scoping", () => {
util.testFunction`
let x = 0;
let result = 0;
do {
let x = 1;
++result;
} while (x === 0 && result < 2);
return result;
`.expectToMatchJsResult();
});
test("do...while double-negation", () => {
const builder = util.testFunction`
let result = 0;
do {
++result;
} while (!(result >= 2));
return result;
`.expectToMatchJsResult();
expect(builder.getMainLuaCodeChunk()).not.toMatch("not");
});
test("for...in with pre-defined variable", () => {
const testBuilder = util.testFunction`
const obj = { x: "y", foo: "bar" };
let x = "";
let result = [];
for (x in obj) {
result.push(x);
}
return result;
`;
// Need custom matcher because order is not guaranteed in neither JS nor Lua
expect(testBuilder.getJsExecutionResult()).toEqual(expect.arrayContaining(testBuilder.getLuaExecutionResult()));
});
test("for...in with pre-defined variable keeps last value", () => {
const keyX = "x";
const keyFoo = "foo";
const result = util.testFunction`
const obj = { ${keyX}: "y", ${keyFoo}: "bar" };
let x = "";
for (x in obj) {
}
return x;
`.getLuaExecutionResult();
// Need custom matcher because order is not guaranteed in neither JS nor Lua
expect([keyX, keyFoo]).toContain(result);
});
// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1631
test("loop variables should not be global (#1631)", () => {
const code = util.testModule`
for (let val = 0; val < 2; ++val) {}
`.getMainLuaCodeChunk();
expect(code).toContain("local val");
});