From 7e26887bf1bb6017a9d86f38c5699a1db1a023fb Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sat, 13 Apr 2019 08:12:20 -0600 Subject: [PATCH 1/4] added ArrayLengthSet lib function --- src/LuaLib.ts | 1 + src/LuaTransformer.ts | 22 ++++++++++++++++++++++ src/TSHelper.ts | 26 ++++++++++++++++++++++++++ src/lualib/ArraySetLength.ts | 9 +++++++++ test/unit/array.spec.ts | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+) create mode 100644 src/lualib/ArraySetLength.ts diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 7515d8d4b..5b259f66b 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -19,6 +19,7 @@ export enum LuaLibFeature { ArraySplice = "ArraySplice", ArrayFlat = "ArrayFlat", ArrayFlatMap = "ArrayFlatMap", + ArraySetLength = "ArraySetLength", ClassIndex = "ClassIndex", ClassNewIndex = "ClassNewIndex", FunctionApply = "FunctionApply", diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 921ce48a5..eede8ed9e 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2494,6 +2494,18 @@ export class LuaTransformer { const leftType = this.checker.getTypeAtLocation(expression.left); this.validateFunctionAssignment(expression.right, rightType, leftType); + if (tsHelper.isArrayLengthAssignment(expression, this.checker, this.program)) { + // array.length = x + return tstl.createExpressionStatement( + this.transformLuaLibFunction( + LuaLibFeature.ArraySetLength, + expression, + this.transformExpression(expression.left.expression), + this.transformExpression(expression.right) + ) + ); + } + if (ts.isArrayLiteralExpression(expression.left)) { // Destructuring assignment const left = expression.left.elements.length > 0 @@ -2528,6 +2540,16 @@ export class LuaTransformer { const leftType = this.checker.getTypeAtLocation(expression.left); this.validateFunctionAssignment(expression.right, rightType, leftType); + if (tsHelper.isArrayLengthAssignment(expression, this.checker, this.program)) { + // array.length = x + return this.transformLuaLibFunction( + LuaLibFeature.ArraySetLength, + expression, + this.transformExpression(expression.left.expression), + this.transformExpression(expression.right) + ); + } + if (ts.isArrayLiteralExpression(expression.left)) { // Destructuring assignment // (function() local ${tmps} = ${right}; ${left} = ${tmps}; return {${tmps}} end)() diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 521b06f22..d50523fa1 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -752,4 +752,30 @@ export class TSHelper { } return false; } + + public static isArrayLengthAssignment( + expression: ts.BinaryExpression, + checker: ts.TypeChecker, + program: ts.Program + ): expression is ts.BinaryExpression & { left: ts.PropertyAccessExpression | ts.ElementAccessExpression; } + { + if (expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) { + return false; + } + + if (!ts.isPropertyAccessExpression(expression.left) && !ts.isElementAccessExpression(expression.left)) { + return false; + } + + const type = checker.getTypeAtLocation(expression.left.expression); + if (!TSHelper.isArrayType(type, checker, program)) { + return false; + } + + const name = ts.isPropertyAccessExpression(expression.left) + ? expression.left.name.escapedText as string + : ts.isStringLiteral(expression.left.argumentExpression) && expression.left.argumentExpression.text; + + return name === "length"; + } } diff --git a/src/lualib/ArraySetLength.ts b/src/lualib/ArraySetLength.ts new file mode 100644 index 000000000..d7d766788 --- /dev/null +++ b/src/lualib/ArraySetLength.ts @@ -0,0 +1,9 @@ +function __TS__ArraySetLength(this: void, arr: T[], length: number): number { + if (length < 0) { + throw `invalid array length: ${length}`; + } + for (let i = arr.length - 1; i >= length; --i) { + arr[i] = undefined; + } + return length; +} diff --git a/test/unit/array.spec.ts b/test/unit/array.spec.ts index 85dcb5dcb..7fa205e56 100644 --- a/test/unit/array.spec.ts +++ b/test/unit/array.spec.ts @@ -136,3 +136,36 @@ test("Array property access", () => { `; expect(util.transpileAndExecute(code)).toBe("bar123"); }); + +test.each([{ length: 0, result: 0 }, { length: 1, result: 1 }, { length: 7, result: 3 }])( + "Array length set", + ({ length, result }) => { + const code = ` + const arr = [1, 2, 3]; + arr.length = ${length}; + return arr.length; + `; + expect(util.transpileAndExecute(code)).toBe(result); + }, +); + +test.each([ + { length: 0, result: "0/0" }, + { length: 1, result: "1/1" }, + { length: 7, result: "7/3" }, +])("Array length set as expression", ({ length, result }) => { + const code = ` + const arr = [1, 2, 3]; + const l = arr.length = ${length}; + return \`\${l}/\${arr.length}\`; + `; + expect(util.transpileAndExecute(code)).toBe(result); +}); + +test.each([-1, -7])("Invalid array length set", length => { + const code = ` + const arr = [1, 2, 3]; + arr.length = ${length}; + `; + expect(() => util.transpileAndExecute(code)).toThrowError(`invalid array length: ${length}`); +}); From 322cb88c32ef163af3b6397e8422cd78e448d44e Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sat, 13 Apr 2019 08:16:03 -0600 Subject: [PATCH 2/4] Disabled tslint error in ArraySetLength --- src/lualib/ArraySetLength.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lualib/ArraySetLength.ts b/src/lualib/ArraySetLength.ts index d7d766788..8b00b56eb 100644 --- a/src/lualib/ArraySetLength.ts +++ b/src/lualib/ArraySetLength.ts @@ -1,5 +1,6 @@ function __TS__ArraySetLength(this: void, arr: T[], length: number): number { if (length < 0) { + // tslint:disable-next-line:no-string-throw throw `invalid array length: ${length}`; } for (let i = arr.length - 1; i >= length; --i) { From 33bc6583051af6cf0d3bb753c44b657660301d6a Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sat, 13 Apr 2019 10:21:13 -0600 Subject: [PATCH 3/4] Handling NaN, Infinity, and non-integers --- src/lualib/ArraySetLength.ts | 7 ++++++- test/unit/array.spec.ts | 11 +++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lualib/ArraySetLength.ts b/src/lualib/ArraySetLength.ts index 8b00b56eb..026cdd067 100644 --- a/src/lualib/ArraySetLength.ts +++ b/src/lualib/ArraySetLength.ts @@ -1,5 +1,10 @@ function __TS__ArraySetLength(this: void, arr: T[], length: number): number { - if (length < 0) { + if (length < 0 + || length !== length // NaN + || length === (1 / 0) // Infinity + || length === (-1 / 0) // -Infinity + || Math.floor(length) !== length) // non-integer + { // tslint:disable-next-line:no-string-throw throw `invalid array length: ${length}`; } diff --git a/test/unit/array.spec.ts b/test/unit/array.spec.ts index 7fa205e56..9da08ce1c 100644 --- a/test/unit/array.spec.ts +++ b/test/unit/array.spec.ts @@ -162,10 +162,17 @@ test.each([ expect(util.transpileAndExecute(code)).toBe(result); }); -test.each([-1, -7])("Invalid array length set", length => { +test.each([ + { length: -1, result: -1 }, + { length: -7, result: -7 }, + { length: 0.1, result: 0.1 }, + { length: "0 / 0", result: "NaN" }, + { length: "1 / 0", result: "Infinity" }, + { length: "-1 / 0", result: "-Infinity" }, +])("Invalid array length set", ({ length, result }) => { const code = ` const arr = [1, 2, 3]; arr.length = ${length}; `; - expect(() => util.transpileAndExecute(code)).toThrowError(`invalid array length: ${length}`); + expect(() => util.transpileAndExecute(code)).toThrowError(`invalid array length: ${result}`); }); From 658d52538a39f578ee43294cdd73272c8792daeb Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sat, 13 Apr 2019 10:25:42 -0600 Subject: [PATCH 4/4] Removed unneeded -Infinity check --- src/lualib/ArraySetLength.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lualib/ArraySetLength.ts b/src/lualib/ArraySetLength.ts index 026cdd067..3efcf1e2f 100644 --- a/src/lualib/ArraySetLength.ts +++ b/src/lualib/ArraySetLength.ts @@ -2,7 +2,6 @@ function __TS__ArraySetLength(this: void, arr: T[], length: number): number { if (length < 0 || length !== length // NaN || length === (1 / 0) // Infinity - || length === (-1 / 0) // -Infinity || Math.floor(length) !== length) // non-integer { // tslint:disable-next-line:no-string-throw