From c9cf2e44ff81eb43c8f493f9b27d29e71e3af078 Mon Sep 17 00:00:00 2001 From: GlassBricks <24237065+GlassBricks@users.noreply.github.com> Date: Thu, 3 Feb 2022 16:11:44 -0800 Subject: [PATCH 1/6] Add benchmarks --- .../src/runtime_benchmarks/array_concat_array.ts | 9 +++++++++ .../src/runtime_benchmarks/array_concat_spread.ts | 9 +++++++++ benchmark/src/runtime_benchmarks/array_every.ts | 7 +++++++ benchmark/src/runtime_benchmarks/array_filter.ts | 7 +++++++ benchmark/src/runtime_benchmarks/array_find.ts | 9 +++++++++ benchmark/src/runtime_benchmarks/array_findIndex.ts | 9 +++++++++ benchmark/src/runtime_benchmarks/array_flat.ts | 7 +++++++ benchmark/src/runtime_benchmarks/array_flatMap.ts | 13 +++++++++++++ benchmark/src/runtime_benchmarks/array_foreach.ts | 10 ++++++++++ benchmark/src/runtime_benchmarks/array_includes.ts | 9 +++++++++ benchmark/src/runtime_benchmarks/array_indexOf.ts | 9 +++++++++ benchmark/src/runtime_benchmarks/array_join.ts | 8 ++++++++ benchmark/src/runtime_benchmarks/array_map.ts | 7 +++++++ .../src/runtime_benchmarks/array_push_array.ts | 9 +++++++++ .../{array_push.ts => array_push_multiple.ts} | 4 ++-- .../src/runtime_benchmarks/array_push_single.ts | 8 ++++++++ .../src/runtime_benchmarks/array_push_vararg.ts | 12 ++++++++++++ benchmark/src/runtime_benchmarks/array_reduce.ts | 7 +++++++ .../src/runtime_benchmarks/array_reduceRight.ts | 7 +++++++ benchmark/src/runtime_benchmarks/array_reverse.ts | 7 +++++++ benchmark/src/runtime_benchmarks/array_slice.ts | 9 +++++++++ benchmark/src/runtime_benchmarks/array_some.ts | 7 +++++++ benchmark/src/runtime_benchmarks/array_splice.ts | 9 +++++++++ benchmark/src/runtime_benchmarks/array_unshift.ts | 9 +++++++++ benchmark/src/runtime_benchmarks/string_concat.ts | 8 ++++++++ benchmark/src/runtime_benchmarks/string_replace.ts | 8 ++++++++ .../src/runtime_benchmarks/string_replaceAll.ts | 8 ++++++++ benchmark/src/runtime_benchmarks/string_split.ts | 8 ++++++++ 28 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 benchmark/src/runtime_benchmarks/array_concat_array.ts create mode 100644 benchmark/src/runtime_benchmarks/array_concat_spread.ts create mode 100644 benchmark/src/runtime_benchmarks/array_every.ts create mode 100644 benchmark/src/runtime_benchmarks/array_filter.ts create mode 100644 benchmark/src/runtime_benchmarks/array_find.ts create mode 100644 benchmark/src/runtime_benchmarks/array_findIndex.ts create mode 100644 benchmark/src/runtime_benchmarks/array_flat.ts create mode 100644 benchmark/src/runtime_benchmarks/array_flatMap.ts create mode 100644 benchmark/src/runtime_benchmarks/array_foreach.ts create mode 100644 benchmark/src/runtime_benchmarks/array_includes.ts create mode 100644 benchmark/src/runtime_benchmarks/array_indexOf.ts create mode 100644 benchmark/src/runtime_benchmarks/array_join.ts create mode 100644 benchmark/src/runtime_benchmarks/array_map.ts create mode 100644 benchmark/src/runtime_benchmarks/array_push_array.ts rename benchmark/src/runtime_benchmarks/{array_push.ts => array_push_multiple.ts} (68%) create mode 100644 benchmark/src/runtime_benchmarks/array_push_single.ts create mode 100644 benchmark/src/runtime_benchmarks/array_push_vararg.ts create mode 100644 benchmark/src/runtime_benchmarks/array_reduce.ts create mode 100644 benchmark/src/runtime_benchmarks/array_reduceRight.ts create mode 100644 benchmark/src/runtime_benchmarks/array_reverse.ts create mode 100644 benchmark/src/runtime_benchmarks/array_slice.ts create mode 100644 benchmark/src/runtime_benchmarks/array_some.ts create mode 100644 benchmark/src/runtime_benchmarks/array_splice.ts create mode 100644 benchmark/src/runtime_benchmarks/array_unshift.ts create mode 100644 benchmark/src/runtime_benchmarks/string_concat.ts create mode 100644 benchmark/src/runtime_benchmarks/string_replace.ts create mode 100644 benchmark/src/runtime_benchmarks/string_replaceAll.ts create mode 100644 benchmark/src/runtime_benchmarks/string_split.ts diff --git a/benchmark/src/runtime_benchmarks/array_concat_array.ts b/benchmark/src/runtime_benchmarks/array_concat_array.ts new file mode 100644 index 000000000..9c9a7e83d --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_concat_array.ts @@ -0,0 +1,9 @@ +export default function arrayConcat(): number[] { + const arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; + const arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; + const n = 50000; + for (let i = 0; i < n; i++) { + arr1.concat(arr2); + } + return arr1; +} diff --git a/benchmark/src/runtime_benchmarks/array_concat_spread.ts b/benchmark/src/runtime_benchmarks/array_concat_spread.ts new file mode 100644 index 000000000..a04eff958 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_concat_spread.ts @@ -0,0 +1,9 @@ +export default function arrayConcat(): number[] { + const arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; + const arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; + const n = 50000; + for (let i = 0; i < n; i++) { + arr1.concat(...arr2); + } + return arr1; +} diff --git a/benchmark/src/runtime_benchmarks/array_every.ts b/benchmark/src/runtime_benchmarks/array_every.ts new file mode 100644 index 000000000..f5dbfc4e4 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_every.ts @@ -0,0 +1,7 @@ +export default function arrayEvery() { + const n = 200000; + const array = [1, 2, 3, 4, 5, 6, 7, 8, 7, 10]; + for (let i = 0; i < n; i++) { + array.every((item, index) => item > index); + } +} diff --git a/benchmark/src/runtime_benchmarks/array_filter.ts b/benchmark/src/runtime_benchmarks/array_filter.ts new file mode 100644 index 000000000..127d2476d --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_filter.ts @@ -0,0 +1,7 @@ +export default function arrayFilter() { + const n = 100000; + const array = [1, 2, 3, 4, 3, 6, 7, 8, 7, 10]; + for (let i = 0; i < n; i++) { + array.filter((item, index) => item > index); + } +} diff --git a/benchmark/src/runtime_benchmarks/array_find.ts b/benchmark/src/runtime_benchmarks/array_find.ts new file mode 100644 index 000000000..8a84ff4e5 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_find.ts @@ -0,0 +1,9 @@ +export default function arrayFind() { + const n = 50000; + const array = [1, 2, 3, 4, 3, 6, 7, 8, 9, 10]; + for (let i = 0; i < n; i++) { + for (let j = 0; j < 10; j++) { + array.find(value => value === j); + } + } +} diff --git a/benchmark/src/runtime_benchmarks/array_findIndex.ts b/benchmark/src/runtime_benchmarks/array_findIndex.ts new file mode 100644 index 000000000..c058a99df --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_findIndex.ts @@ -0,0 +1,9 @@ +export default function arrayFindIndex() { + const n = 50000; + const array = [1, 2, 3, 4, 3, 6, 7, 8, 9, 10]; + for (let i = 0; i < n; i++) { + for (let j = 0; j < 10; j++) { + array.findIndex(value => value === j); + } + } +} diff --git a/benchmark/src/runtime_benchmarks/array_flat.ts b/benchmark/src/runtime_benchmarks/array_flat.ts new file mode 100644 index 000000000..36ace2d95 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_flat.ts @@ -0,0 +1,7 @@ +export default function arrayFlat() { + const n = 50000; + const array = [1, 2, [3, [4, 5], 6], 7, [8, 9], 10]; + for (let i = 0; i < n; i++) { + array.flat(2); + } +} diff --git a/benchmark/src/runtime_benchmarks/array_flatMap.ts b/benchmark/src/runtime_benchmarks/array_flatMap.ts new file mode 100644 index 000000000..26c6490e5 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_flatMap.ts @@ -0,0 +1,13 @@ +export default function arrayFlatMap() { + const n = 50000; + const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + for (let i = 0; i < n; i++) { + array.flatMap((el, index) => { + if (index < 5) { + return [el, el + 1]; + } else { + return el + 2; + } + }); + } +} diff --git a/benchmark/src/runtime_benchmarks/array_foreach.ts b/benchmark/src/runtime_benchmarks/array_foreach.ts new file mode 100644 index 000000000..8e14c8119 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_foreach.ts @@ -0,0 +1,10 @@ +export default function arrayForeach() { + const n = 200000; + const array = [1, 2, 3, 4, 3, 6, 7, 8, 9, 10]; + for (let i = 0; i < n; i++) { + array.forEach(value => { + let foo = value * 2; + foo = foo; + }); + } +} diff --git a/benchmark/src/runtime_benchmarks/array_includes.ts b/benchmark/src/runtime_benchmarks/array_includes.ts new file mode 100644 index 000000000..828c167a3 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_includes.ts @@ -0,0 +1,9 @@ +export default function arrayIncludes() { + const n = 50000; + const array = [1, 2, 3, 4, 3, 6, 7, 8, 9, 10]; + for (let i = 0; i < n; i++) { + for (let j = 0; j < 10; j++) { + array.includes(j); + } + } +} diff --git a/benchmark/src/runtime_benchmarks/array_indexOf.ts b/benchmark/src/runtime_benchmarks/array_indexOf.ts new file mode 100644 index 000000000..333c67988 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_indexOf.ts @@ -0,0 +1,9 @@ +export default function arrayIndexOf() { + const n = 50000; + const array = [1, 2, 3, 4, 3, 6, 7, 8, 9, 10]; + for (let i = 0; i < n; i++) { + for (let j = 0; j < 10; j++) { + array.indexOf(j); + } + } +} diff --git a/benchmark/src/runtime_benchmarks/array_join.ts b/benchmark/src/runtime_benchmarks/array_join.ts new file mode 100644 index 000000000..5a9f5bae4 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_join.ts @@ -0,0 +1,8 @@ +const array = [1, 2, "3", 4, 3, "6", { foo: 3 }, 8, 9, 10]; + +export default function arrayJoin() { + const n = 3000; + for (let i = 0; i < n; i++) { + array.join("|"); + } +} diff --git a/benchmark/src/runtime_benchmarks/array_map.ts b/benchmark/src/runtime_benchmarks/array_map.ts new file mode 100644 index 000000000..79d00d66a --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_map.ts @@ -0,0 +1,7 @@ +export default function arrayMap() { + const n = 100000; + const array = [1, 2, 3, 4, 3, 6, 7, 8, 9, 10]; + for (let i = 0; i < n; i++) { + array.map(value => value * 2); + } +} diff --git a/benchmark/src/runtime_benchmarks/array_push_array.ts b/benchmark/src/runtime_benchmarks/array_push_array.ts new file mode 100644 index 000000000..fcc239a31 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_push_array.ts @@ -0,0 +1,9 @@ +export default function arrayPush(): number[] { + const n = 200000; + const numberList: number[] = []; + const numbers = [1, 2, 3]; + for (let i = 0; i < n; i++) { + numberList.push(...numbers); + } + return numberList; +} diff --git a/benchmark/src/runtime_benchmarks/array_push.ts b/benchmark/src/runtime_benchmarks/array_push_multiple.ts similarity index 68% rename from benchmark/src/runtime_benchmarks/array_push.ts rename to benchmark/src/runtime_benchmarks/array_push_multiple.ts index e0d2d0b87..dbef5460c 100644 --- a/benchmark/src/runtime_benchmarks/array_push.ts +++ b/benchmark/src/runtime_benchmarks/array_push_multiple.ts @@ -1,8 +1,8 @@ export default function arrayPush(): number[] { - const n = 1000000; + const n = 200000; const numberList: number[] = []; for (let i = 0; i < n; i++) { - numberList[numberList.length] = i * i; + numberList.push(i * i, i + 1); } return numberList; } diff --git a/benchmark/src/runtime_benchmarks/array_push_single.ts b/benchmark/src/runtime_benchmarks/array_push_single.ts new file mode 100644 index 000000000..f09bc21ec --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_push_single.ts @@ -0,0 +1,8 @@ +export default function arrayPush(): number[] { + const n = 500000; + const numberList: number[] = []; + for (let i = 0; i < n; i++) { + numberList.push(i * i); + } + return numberList; +} diff --git a/benchmark/src/runtime_benchmarks/array_push_vararg.ts b/benchmark/src/runtime_benchmarks/array_push_vararg.ts new file mode 100644 index 000000000..e780461ee --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_push_vararg.ts @@ -0,0 +1,12 @@ +export default function arrayPush(): number[] { + const n = 200000; + const numberList: number[] = []; + function pushArgs(...args: number[]): void { + numberList.push(...args); + } + + for (let i = 0; i < n; i++) { + pushArgs(3, 4); + } + return numberList; +} diff --git a/benchmark/src/runtime_benchmarks/array_reduce.ts b/benchmark/src/runtime_benchmarks/array_reduce.ts new file mode 100644 index 000000000..2d615e80f --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_reduce.ts @@ -0,0 +1,7 @@ +export default function arrayReduce() { + const n = 200000; + const array = [1, 2, 3, 4, 5, 6, 7, 8, 7, 10]; + for (let i = 0; i < n; i++) { + array.reduce((prev, cur, i) => prev + cur + i, 1); + } +} diff --git a/benchmark/src/runtime_benchmarks/array_reduceRight.ts b/benchmark/src/runtime_benchmarks/array_reduceRight.ts new file mode 100644 index 000000000..9dd97d3d2 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_reduceRight.ts @@ -0,0 +1,7 @@ +export default function arrayReduce() { + const n = 200000; + const array = [1, 2, 3, 4, 5, 6, 7, 8, 7, 10]; + for (let i = 0; i < n; i++) { + array.reduceRight((prev, cur, i) => prev + cur + i, 1); + } +} diff --git a/benchmark/src/runtime_benchmarks/array_reverse.ts b/benchmark/src/runtime_benchmarks/array_reverse.ts new file mode 100644 index 000000000..539d26138 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_reverse.ts @@ -0,0 +1,7 @@ +export default function arrayReverse(): void { + const n = 500000; + const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + for (let i = 0; i < n; i++) { + numbers.reverse(); + } +} diff --git a/benchmark/src/runtime_benchmarks/array_slice.ts b/benchmark/src/runtime_benchmarks/array_slice.ts new file mode 100644 index 000000000..5419cb0d9 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_slice.ts @@ -0,0 +1,9 @@ +export default function arraySlice() { + const n = 50000; + const array = [1, 2, 3, 4, 3, 6, 7, 8, 9, 10]; + for (let i = 0; i < n; i++) { + for (let j = 0; j < 6; j++) { + array.slice(j, -j); + } + } +} diff --git a/benchmark/src/runtime_benchmarks/array_some.ts b/benchmark/src/runtime_benchmarks/array_some.ts new file mode 100644 index 000000000..8add42a56 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_some.ts @@ -0,0 +1,7 @@ +export default function arraySome() { + const n = 200000; + const array = [1, 2, 3, 4, 5, 6, 7, 8, 7, 10]; + for (let i = 0; i < n; i++) { + array.some((item, index) => item < index); + } +} diff --git a/benchmark/src/runtime_benchmarks/array_splice.ts b/benchmark/src/runtime_benchmarks/array_splice.ts new file mode 100644 index 000000000..4620bad86 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_splice.ts @@ -0,0 +1,9 @@ +export default function arraySplice() { + const n = 20000; + const array = [1, 2, 3, 4, 3, 6, 7, 8, 9, 10]; + for (let i = 0; i < n; i++) { + for (let j = 0; j < 10; j++) { + array.splice(j, 2, 1, 2); + } + } +} diff --git a/benchmark/src/runtime_benchmarks/array_unshift.ts b/benchmark/src/runtime_benchmarks/array_unshift.ts new file mode 100644 index 000000000..b0f59a0b2 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/array_unshift.ts @@ -0,0 +1,9 @@ +export default function arrayUnshift(): number[] { + const n = 2000; + const numberList: number[] = []; + const numbers = [1, 2, 3]; + for (let i = 0; i < n; i++) { + numberList.unshift(...numbers); + } + return numberList; +} diff --git a/benchmark/src/runtime_benchmarks/string_concat.ts b/benchmark/src/runtime_benchmarks/string_concat.ts new file mode 100644 index 000000000..47d520e9a --- /dev/null +++ b/benchmark/src/runtime_benchmarks/string_concat.ts @@ -0,0 +1,8 @@ +export default function stringReplace() { + const str = "one"; + const n = 50000; + for (let i = 0; i < n; i++) { + str.concat("two", "three", "four", "five"); + } + return str; +} diff --git a/benchmark/src/runtime_benchmarks/string_replace.ts b/benchmark/src/runtime_benchmarks/string_replace.ts new file mode 100644 index 000000000..e7a00fc35 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/string_replace.ts @@ -0,0 +1,8 @@ +export default function stringReplace() { + const str = "Hello, World!"; + const n = 50000; + for (let i = 0; i < n; i++) { + str.replace("World", "Universe"); + } + return str; +} diff --git a/benchmark/src/runtime_benchmarks/string_replaceAll.ts b/benchmark/src/runtime_benchmarks/string_replaceAll.ts new file mode 100644 index 000000000..268bb3642 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/string_replaceAll.ts @@ -0,0 +1,8 @@ +export default function stringReplaceAll() { + const str = "hello, hello world!"; + const n = 50000; + for (let i = 0; i < n; i++) { + str.replaceAll("Hello", "Goodbye"); + } + return str; +} diff --git a/benchmark/src/runtime_benchmarks/string_split.ts b/benchmark/src/runtime_benchmarks/string_split.ts new file mode 100644 index 000000000..22f6c9bb1 --- /dev/null +++ b/benchmark/src/runtime_benchmarks/string_split.ts @@ -0,0 +1,8 @@ +export default function stringReplaceAll() { + const str = "This is a string"; + const n = 50000; + for (let i = 0; i < n; i++) { + str.split(" "); + } + return str; +} From 5004ee05cd55c143d6c071a24ef182e9037207c0 Mon Sep 17 00:00:00 2001 From: GlassBricks <24237065+GlassBricks@users.noreply.github.com> Date: Thu, 3 Feb 2022 16:14:27 -0800 Subject: [PATCH 2/6] Optimize lualib functions --- src/LuaLib.ts | 2 - src/lualib/ArrayConcat.ts | 26 ++++--- src/lualib/ArrayEvery.ts | 10 +-- src/lualib/ArrayFilter.ts | 14 ++-- src/lualib/ArrayFind.ts | 15 ++-- src/lualib/ArrayFindIndex.ts | 12 ++-- src/lualib/ArrayFlat.ts | 23 ++++-- src/lualib/ArrayFlatMap.ts | 23 +++--- src/lualib/ArrayForEach.ts | 10 +-- src/lualib/ArrayIncludes.ts | 4 +- src/lualib/ArrayIndexOf.ts | 28 +++----- src/lualib/ArrayIsArray.ts | 4 +- src/lualib/ArrayJoin.ts | 11 ++- src/lualib/ArrayMap.ts | 14 ++-- src/lualib/ArrayPush.ts | 10 +-- src/lualib/ArrayReduce.ts | 13 ++-- src/lualib/ArrayReduceRight.ts | 13 ++-- src/lualib/ArrayReverse.ts | 18 ++--- src/lualib/ArraySetLength.ts | 6 +- src/lualib/ArrayShift.ts | 3 - src/lualib/ArraySlice.ts | 47 +++++++------ src/lualib/ArraySome.ts | 10 +-- src/lualib/ArraySort.ts | 8 +-- src/lualib/ArraySplice.ts | 74 +++++++++++--------- src/lualib/ArrayToObject.ts | 6 +- src/lualib/ArrayUnshift.ts | 14 ++-- src/lualib/FunctionBind.ts | 7 +- src/lualib/ObjectAssign.ts | 13 ++-- src/lualib/ObjectEntries.ts | 4 +- src/lualib/ObjectKeys.ts | 4 +- src/lualib/ObjectValues.ts | 4 +- src/lualib/Spread.ts | 8 ++- src/lualib/StringConcat.ts | 7 -- src/lualib/StringReplace.ts | 2 +- src/lualib/StringReplaceAll.ts | 19 ++--- src/lualib/StringSplit.ts | 36 +++++----- src/transformation/builtins/array.ts | 57 +++++++++++++-- src/transformation/builtins/string.ts | 8 ++- src/transformation/utils/typescript/index.ts | 8 +++ src/transformation/visitors/spread.ts | 11 +-- test/unit/__snapshots__/switch.spec.ts.snap | 32 +++------ test/unit/builtins/array.spec.ts | 4 +- test/unit/builtins/loading.spec.ts | 4 +- 43 files changed, 356 insertions(+), 290 deletions(-) delete mode 100644 src/lualib/ArrayShift.ts delete mode 100644 src/lualib/StringConcat.ts diff --git a/src/LuaLib.ts b/src/LuaLib.ts index e5df8927a..2584f0826 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -19,7 +19,6 @@ export enum LuaLibFeature { ArrayReduce = "ArrayReduce", ArrayReduceRight = "ArrayReduceRight", ArrayReverse = "ArrayReverse", - ArrayShift = "ArrayShift", ArrayUnshift = "ArrayUnshift", ArraySort = "ArraySort", ArraySlice = "ArraySlice", @@ -79,7 +78,6 @@ export enum LuaLibFeature { StringAccess = "StringAccess", StringCharAt = "StringCharAt", StringCharCodeAt = "StringCharCodeAt", - StringConcat = "StringConcat", StringEndsWith = "StringEndsWith", StringIncludes = "StringIncludes", StringPadEnd = "StringPadEnd", diff --git a/src/lualib/ArrayConcat.ts b/src/lualib/ArrayConcat.ts index 56f3d1b5f..caaafa369 100644 --- a/src/lualib/ArrayConcat.ts +++ b/src/lualib/ArrayConcat.ts @@ -1,18 +1,22 @@ -export function __TS__ArrayConcat(this: void, arr1: any[], ...args: any[]): any[] { - const out: any[] = []; - for (const val of arr1) { - out[out.length] = val; +export function __TS__ArrayConcat(this: T[], ...items: Array): T[] { + const result: T[] = []; + let len = 0; + for (const i of $range(1, this.length)) { + len++; + result[len - 1] = this[i - 1]; } - for (const arg of args) { - if (Array.isArray(arg)) { - const argAsArray = arg; - for (const val of argAsArray) { - out[out.length] = val; + for (const i of $range(1, items.length)) { + const item = items[i - 1]; + if (Array.isArray(item)) { + for (const j of $range(1, item.length)) { + len++; + result[len - 1] = item[j - 1]; } } else { - out[out.length] = arg; + len++; + result[len - 1] = item; } } - return out; + return result; } diff --git a/src/lualib/ArrayEvery.ts b/src/lualib/ArrayEvery.ts index 43dd312ea..d94efec54 100644 --- a/src/lualib/ArrayEvery.ts +++ b/src/lualib/ArrayEvery.ts @@ -1,10 +1,10 @@ export function __TS__ArrayEvery( - this: void, - arr: T[], - callbackfn: (value: T, index?: number, array?: any[]) => boolean + this: T[], + callbackfn: (value: T, index?: number, array?: any[]) => boolean, + thisArg?: any ): boolean { - for (let i = 0; i < arr.length; i++) { - if (!callbackfn(arr[i], i, arr)) { + for (const i of $range(1, this.length)) { + if (!callbackfn.call(thisArg, this[i - 1], i - 1, this)) { return false; } } diff --git a/src/lualib/ArrayFilter.ts b/src/lualib/ArrayFilter.ts index 0559a02d6..5d63a0494 100644 --- a/src/lualib/ArrayFilter.ts +++ b/src/lualib/ArrayFilter.ts @@ -1,12 +1,14 @@ export function __TS__ArrayFilter( - this: void, - arr: T[], - callbackfn: (value: T, index?: number, array?: any[]) => boolean + this: T[], + callbackfn: (value: T, index?: number, array?: any[]) => boolean, + thisArg?: any ): T[] { const result: T[] = []; - for (let i = 0; i < arr.length; i++) { - if (callbackfn(arr[i], i, arr)) { - result[result.length] = arr[i]; + let len = 0; + for (const i of $range(1, this.length)) { + if (callbackfn.call(thisArg, this[i - 1], i - 1, this)) { + len++; + result[len - 1] = this[i - 1]; } } return result; diff --git a/src/lualib/ArrayFind.ts b/src/lualib/ArrayFind.ts index a9a0efc70..3beb9c83b 100644 --- a/src/lualib/ArrayFind.ts +++ b/src/lualib/ArrayFind.ts @@ -1,17 +1,14 @@ // https://www.ecma-international.org/ecma-262/10.0/index.html#sec-array.prototype.find export function __TS__ArrayFind( - this: void, - arr: T[], - predicate: (value: T, index: number, obj: T[]) => unknown + this: T[], + predicate: (value: T, index: number, obj: T[]) => unknown, + thisArg?: any ): T | undefined { - const len = arr.length; - let k = 0; - while (k < len) { - const elem = arr[k]; - if (predicate(elem, k, arr)) { + for (const i of $range(1, this.length)) { + const elem = this[i - 1]; + if (predicate.call(thisArg, elem, i - 1, this)) { return elem; } - k += 1; } return undefined; diff --git a/src/lualib/ArrayFindIndex.ts b/src/lualib/ArrayFindIndex.ts index 945fed18d..3741f3a0f 100644 --- a/src/lualib/ArrayFindIndex.ts +++ b/src/lualib/ArrayFindIndex.ts @@ -1,11 +1,11 @@ export function __TS__ArrayFindIndex( - this: void, - arr: T[], - callbackFn: (element: T, index?: number, array?: T[]) => boolean + this: T[], + callbackFn: (element: T, index?: number, array?: T[]) => boolean, + thisArg?: any ): number { - for (let i = 0, len = arr.length; i < len; i++) { - if (callbackFn(arr[i], i, arr)) { - return i; + for (const i of $range(1, this.length)) { + if (callbackFn.call(thisArg, this[i - 1], i - 1, this)) { + return i - 1; } } return -1; diff --git a/src/lualib/ArrayFlat.ts b/src/lualib/ArrayFlat.ts index b98861aeb..3e15b455b 100644 --- a/src/lualib/ArrayFlat.ts +++ b/src/lualib/ArrayFlat.ts @@ -1,10 +1,23 @@ -export function __TS__ArrayFlat(this: void, array: any[], depth = 1): any[] { - let result: any[] = []; - for (const value of array) { +export function __TS__ArrayFlat(this: any[], depth = 1): any[] { + const result: any[] = []; + let len = 0; + for (const i of $range(1, this.length)) { + const value = this[i - 1]; if (depth > 0 && Array.isArray(value)) { - result = result.concat(__TS__ArrayFlat(value, depth - 1)); + let toAdd: any[]; + if (depth === 1) { + toAdd = value; + } else { + toAdd = value.flat(depth - 1); + } + for (const j of $range(1, toAdd.length)) { + const val = toAdd[j - 1]; + len++; + result[len - 1] = val; + } } else { - result[result.length] = value; + len++; + result[len - 1] = value; } } diff --git a/src/lualib/ArrayFlatMap.ts b/src/lualib/ArrayFlatMap.ts index 29f92c1f5..fa68fce8f 100644 --- a/src/lualib/ArrayFlatMap.ts +++ b/src/lualib/ArrayFlatMap.ts @@ -1,15 +1,20 @@ export function __TS__ArrayFlatMap( - this: void, - array: T[], - callback: (value: T, index: number, array: T[]) => U | readonly U[] + this: T[], + callback: (value: T, index: number, array: T[]) => U | readonly U[], + thisArg?: any ): U[] { - let result: U[] = []; - for (let i = 0; i < array.length; i++) { - const value = callback(array[i], i, array); - if (type(value) === "table" && Array.isArray(value)) { - result = result.concat(value); + const result: U[] = []; + let len = 0; + for (const i of $range(1, this.length)) { + const value = callback.call(thisArg, this[i - 1], i - 1, this); + if (Array.isArray(value)) { + for (const j of $range(1, value.length)) { + len++; + result[len - 1] = value[j - 1]; + } } else { - result[result.length] = value as U; + len++; + result[len - 1] = value as U; } } diff --git a/src/lualib/ArrayForEach.ts b/src/lualib/ArrayForEach.ts index 37a610433..caf4ec53e 100644 --- a/src/lualib/ArrayForEach.ts +++ b/src/lualib/ArrayForEach.ts @@ -1,9 +1,9 @@ export function __TS__ArrayForEach( - this: void, - arr: T[], - callbackFn: (value: T, index?: number, array?: any[]) => any + this: T[], + callbackFn: (value: T, index?: number, array?: any[]) => any, + thisArg?: any ): void { - for (let i = 0; i < arr.length; i++) { - callbackFn(arr[i], i, arr); + for (const i of $range(1, this.length)) { + callbackFn.call(thisArg, this[i - 1], i - 1, this); } } diff --git a/src/lualib/ArrayIncludes.ts b/src/lualib/ArrayIncludes.ts index 88e788a15..b3fd871f6 100644 --- a/src/lualib/ArrayIncludes.ts +++ b/src/lualib/ArrayIncludes.ts @@ -11,8 +11,8 @@ export function __TS__ArrayIncludes(this: T[], searchElement: T, fromIndex = k = 0; } - for (const i of $range(k, len)) { - if (this[i] === searchElement) { + for (const i of $range(k + 1, len)) { + if (this[i - 1] === searchElement) { return true; } } diff --git a/src/lualib/ArrayIndexOf.ts b/src/lualib/ArrayIndexOf.ts index 1631caf76..0a3ebc1f9 100644 --- a/src/lualib/ArrayIndexOf.ts +++ b/src/lualib/ArrayIndexOf.ts @@ -1,31 +1,23 @@ -export function __TS__ArrayIndexOf(this: void, arr: T[], searchElement: T, fromIndex?: number): number { - const len = arr.length; +export function __TS__ArrayIndexOf(this: T[], searchElement: T, fromIndex = 0): number { + const len = this.length; if (len === 0) { return -1; } - let n = 0; - if (fromIndex) { - n = fromIndex; - } - - if (n >= len) { + if (fromIndex >= len) { return -1; } - let k: number; - if (n >= 0) { - k = n; - } else { - k = len + n; - if (k < 0) { - k = 0; + if (fromIndex < 0) { + fromIndex = len + fromIndex; + if (fromIndex < 0) { + fromIndex = 0; } } - for (let i = k; i < len; i++) { - if (arr[i] === searchElement) { - return i; + for (const i of $range(fromIndex + 1, len)) { + if (this[i - 1] === searchElement) { + return i - 1; } } diff --git a/src/lualib/ArrayIsArray.ts b/src/lualib/ArrayIsArray.ts index 7d2ec3112..61fca760b 100644 --- a/src/lualib/ArrayIsArray.ts +++ b/src/lualib/ArrayIsArray.ts @@ -1,7 +1,7 @@ -declare type NextEmptyCheck = (this: void, table: any, index: undefined) => unknown | undefined; +declare type NextEmptyCheck = (this: void, table: any, index?: undefined) => unknown | undefined; export function __TS__ArrayIsArray(this: void, value: any): value is any[] { // Workaround to determine if value is an array or not (fails in case of objects without keys) // See discussion in: https://github.com/TypeScriptToLua/TypeScriptToLua/pull/737 - return type(value) === "table" && (1 in value || (next as NextEmptyCheck)(value, undefined) === undefined); + return type(value) === "table" && (1 in value || (next as NextEmptyCheck)(value) === undefined); } diff --git a/src/lualib/ArrayJoin.ts b/src/lualib/ArrayJoin.ts index 7400b886e..0f0b11c9f 100644 --- a/src/lualib/ArrayJoin.ts +++ b/src/lualib/ArrayJoin.ts @@ -1,8 +1,7 @@ -export function __TS__ArrayJoin(this: unknown[], separator = ",") { - let result = ""; - for (const [index, value] of ipairs(this)) { - if (index > 1) result += separator; - result += value.toString(); +export function __TS__ArrayJoin(this: any[], separator = ",") { + const parts: string[] = []; + for (const i of $range(1, this.length)) { + parts[i - 1] = this[i - 1].toString(); } - return result; + return table.concat(parts, separator); } diff --git a/src/lualib/ArrayMap.ts b/src/lualib/ArrayMap.ts index 7e9c868fa..2caf84028 100644 --- a/src/lualib/ArrayMap.ts +++ b/src/lualib/ArrayMap.ts @@ -1,11 +1,11 @@ export function __TS__ArrayMap( - this: void, - arr: T[], - callbackfn: (value: T, index?: number, array?: T[]) => U + this: T[], + callbackfn: (value: T, index?: number, array?: T[]) => U, + thisArg?: any ): U[] { - const newArray: U[] = []; - for (let i = 0; i < arr.length; i++) { - newArray[i] = callbackfn(arr[i], i, arr); + const result: U[] = []; + for (const i of $range(1, this.length)) { + result[i - 1] = callbackfn.call(thisArg, this[i - 1], i - 1, this); } - return newArray; + return result; } diff --git a/src/lualib/ArrayPush.ts b/src/lualib/ArrayPush.ts index 39d4f241e..9f181d1f2 100644 --- a/src/lualib/ArrayPush.ts +++ b/src/lualib/ArrayPush.ts @@ -1,6 +1,8 @@ -export function __TS__ArrayPush(this: void, arr: T[], ...items: T[]): number { - for (const item of items) { - arr[arr.length] = item; +export function __TS__ArrayPush(this: T[], ...items: T[]): number { + let len = this.length; + for (const i of $range(1, items.length)) { + len++; + this[len - 1] = items[i - 1]; } - return arr.length; + return len; } diff --git a/src/lualib/ArrayReduce.ts b/src/lualib/ArrayReduce.ts index 737128cd0..eb26a9731 100644 --- a/src/lualib/ArrayReduce.ts +++ b/src/lualib/ArrayReduce.ts @@ -1,27 +1,26 @@ // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-array.prototype.reduce export function __TS__ArrayReduce( - this: void, - arr: TElement[], + this: TElement[], callbackFn: (accumulator: TAccumulator, currentValue: TElement, index: number, array: TElement[]) => TAccumulator, ...initial: TAccumulator[] ): TAccumulator { - const len = arr.length; + const len = this.length; let k = 0; let accumulator: TAccumulator = undefined; // Check if initial value is present in function call if (select("#", ...initial) !== 0) { - [accumulator] = select(1, ...initial); + [accumulator] = [...initial]; } else if (len > 0) { - accumulator = arr[0] as unknown as TAccumulator; + accumulator = this[0] as unknown as TAccumulator; k = 1; } else { throw "Reduce of empty array with no initial value"; } - for (const i of $range(k, len - 1)) { - accumulator = callbackFn(accumulator, arr[i], i, arr); + for (const i of $range(k + 1, len)) { + accumulator = callbackFn(accumulator, this[i - 1], i - 1, this); } return accumulator; diff --git a/src/lualib/ArrayReduceRight.ts b/src/lualib/ArrayReduceRight.ts index 91099946a..e32e87e79 100644 --- a/src/lualib/ArrayReduceRight.ts +++ b/src/lualib/ArrayReduceRight.ts @@ -1,27 +1,26 @@ // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-array.prototype.reduce export function __TS__ArrayReduceRight( - this: void, - arr: TElement[], + this: TElement[], callbackFn: (accumulator: TAccumulator, currentValue: TElement, index: number, array: TElement[]) => TAccumulator, ...initial: TAccumulator[] ): TAccumulator { - const len = arr.length; + const len = this.length; let k = len - 1; let accumulator: TAccumulator = undefined; // Check if initial value is present in function call if (select("#", ...initial) !== 0) { - [accumulator] = select(1, ...initial); + [accumulator] = [...initial]; } else if (len > 0) { - accumulator = arr[k] as unknown as TAccumulator; + accumulator = this[k] as unknown as TAccumulator; k -= 1; } else { throw "Reduce of empty array with no initial value"; } - for (const i of $range(k, 0, -1)) { - accumulator = callbackFn(accumulator, arr[i], i, arr); + for (const i of $range(k + 1, 1, -1)) { + accumulator = callbackFn(accumulator, this[i - 1], i - 1, this); } return accumulator; diff --git a/src/lualib/ArrayReverse.ts b/src/lualib/ArrayReverse.ts index e644355d9..96403753b 100644 --- a/src/lualib/ArrayReverse.ts +++ b/src/lualib/ArrayReverse.ts @@ -1,12 +1,12 @@ -export function __TS__ArrayReverse(this: void, arr: any[]): any[] { - let i = 0; - let j = arr.length - 1; +export function __TS__ArrayReverse(this: any[]): any[] { + let i = 1; + let j = this.length; while (i < j) { - const temp = arr[j]; - arr[j] = arr[i]; - arr[i] = temp; - i += 1; - j -= 1; + const temp = this[j - 1]; + this[j - 1] = this[i - 1]; + this[i - 1] = temp; + i++; + j--; } - return arr; + return this; } diff --git a/src/lualib/ArraySetLength.ts b/src/lualib/ArraySetLength.ts index 6fde3254d..4ec78715d 100644 --- a/src/lualib/ArraySetLength.ts +++ b/src/lualib/ArraySetLength.ts @@ -1,4 +1,4 @@ -export function __TS__ArraySetLength(this: void, arr: T[], length: number): number { +export function __TS__ArraySetLength(this: T[], length: number): number { if ( length < 0 || length !== length || // NaN @@ -8,8 +8,8 @@ export function __TS__ArraySetLength(this: void, arr: T[], length: number): n // non-integer throw `invalid array length: ${length}`; } - for (let i = arr.length - 1; i >= length; --i) { - arr[i] = undefined; + for (const i of $range(length + 1, this.length)) { + this[i - 1] = undefined; } return length; } diff --git a/src/lualib/ArrayShift.ts b/src/lualib/ArrayShift.ts deleted file mode 100644 index 799463d9d..000000000 --- a/src/lualib/ArrayShift.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function __TS__ArrayShift(this: void, arr: T[]): T { - return table.remove(arr, 1); -} diff --git a/src/lualib/ArraySlice.ts b/src/lualib/ArraySlice.ts index e9797c43f..e8c357e7f 100644 --- a/src/lualib/ArraySlice.ts +++ b/src/lualib/ArraySlice.ts @@ -1,34 +1,39 @@ // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-array.prototype.slice -export function __TS__ArraySlice(this: void, list: T[], first: number, last: number): T[] { - const len = list.length; +export function __TS__ArraySlice(this: T[], first?: number, last?: number): T[] { + const len = this.length; - const relativeStart = first || 0; - - let k: number; - if (relativeStart < 0) { - k = Math.max(len + relativeStart, 0); + first = first ?? 0; + if (first < 0) { + first = len + first; + if (first < 0) { + first = 0; + } } else { - k = Math.min(relativeStart, len); - } - - let relativeEnd = last; - if (last === undefined) { - relativeEnd = len; + if (first > len) { + first = len; + } } - let final: number; - if (relativeEnd < 0) { - final = Math.max(len + relativeEnd, 0); + last = last ?? len; + if (last < 0) { + last = len + last; + if (last < 0) { + last = 0; + } } else { - final = Math.min(relativeEnd, len); + if (last > len) { + last = len; + } } const out = []; - let n = 0; - while (k < final) { - out[n] = list[k]; - k++; + first++; + last++; + let n = 1; + while (first < last) { + out[n - 1] = this[first - 1]; + first++; n++; } return out; diff --git a/src/lualib/ArraySome.ts b/src/lualib/ArraySome.ts index 1c4725d4c..33ffe69ab 100644 --- a/src/lualib/ArraySome.ts +++ b/src/lualib/ArraySome.ts @@ -1,10 +1,10 @@ export function __TS__ArraySome( - this: void, - arr: T[], - callbackfn: (value: T, index?: number, array?: any[]) => boolean + this: T[], + callbackfn: (value: T, index?: number, array?: any[]) => boolean, + thisArg?: any ): boolean { - for (let i = 0; i < arr.length; i++) { - if (callbackfn(arr[i], i, arr)) { + for (const i of $range(1, this.length)) { + if (callbackfn.call(thisArg, this[i - 1], i - 1, this)) { return true; } } diff --git a/src/lualib/ArraySort.ts b/src/lualib/ArraySort.ts index 034344f76..2a6495511 100644 --- a/src/lualib/ArraySort.ts +++ b/src/lualib/ArraySort.ts @@ -1,8 +1,8 @@ -export function __TS__ArraySort(this: void, arr: T[], compareFn?: (a: T, b: T) => number): T[] { +export function __TS__ArraySort(this: T[], compareFn?: (a: T, b: T) => number): T[] { if (compareFn !== undefined) { - table.sort(arr, (a, b) => compareFn(a, b) < 0); + table.sort(this, (a, b) => compareFn(a, b) < 0); } else { - table.sort(arr); + table.sort(this); } - return arr; + return this; } diff --git a/src/lualib/ArraySplice.ts b/src/lualib/ArraySplice.ts index 273525ed0..82900759d 100644 --- a/src/lualib/ArraySplice.ts +++ b/src/lualib/ArraySplice.ts @@ -1,20 +1,24 @@ // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-array.prototype.splice -export function __TS__ArraySplice(this: void, list: T[], ...args: T[]): T[] { - const len = list.length; +export function __TS__ArraySplice(this: T[], ...args: any[]): T[] { + const len = this.length; const actualArgumentCount = select("#", ...args); - const start = select(1, ...args)[0] as unknown as number; - const deleteCount = select(2, ...args)[0] as unknown as number; - - let actualStart: number; + let start = args[0] as number; + const deleteCount = args[1] as number; if (start < 0) { - actualStart = Math.max(len + start, 0); - } else { - actualStart = Math.min(start, len); + start = len + start; + if (start < 0) { + start = 0; + } + } else if (start > len) { + start = len; } - const itemCount = Math.max(actualArgumentCount - 2, 0); + let itemCount = actualArgumentCount - 2; + if (itemCount < 0) { + itemCount = 0; + } let actualDeleteCount: number; @@ -23,56 +27,62 @@ export function __TS__ArraySplice(this: void, list: T[], ...args: T[]): T[] { actualDeleteCount = 0; } else if (actualArgumentCount === 1) { // ECMA-spec line 6: if number of actual arguments is 1 - actualDeleteCount = len - actualStart; + actualDeleteCount = len - start; } else { - actualDeleteCount = Math.min(Math.max(deleteCount || 0, 0), len - actualStart); + actualDeleteCount = deleteCount || 0; + if (actualDeleteCount < 0) { + actualDeleteCount = 0; + } + if (actualDeleteCount > len - start) { + actualDeleteCount = len - start; + } } const out: T[] = []; - for (let k = 0; k < actualDeleteCount; k++) { - const from = actualStart + k; + for (const k of $range(1, actualDeleteCount)) { + const from = start + k; - if (list[from]) { - out[k] = list[from]; + if (this[from - 1] !== undefined) { + out[k - 1] = this[from - 1]; } } if (itemCount < actualDeleteCount) { - for (let k = actualStart; k < len - actualDeleteCount; k++) { + for (const k of $range(start + 1, len - actualDeleteCount)) { const from = k + actualDeleteCount; const to = k + itemCount; - if (list[from]) { - list[to] = list[from]; + if (this[from - 1]) { + this[to - 1] = this[from - 1]; } else { - list[to] = undefined; + this[to - 1] = undefined; } } - for (let k = len; k > len - actualDeleteCount + itemCount; k--) { - list[k - 1] = undefined; + for (const k of $range(len - actualDeleteCount + itemCount + 1, len)) { + this[k - 1] = undefined; } } else if (itemCount > actualDeleteCount) { - for (let k = len - actualDeleteCount; k > actualStart; k--) { - const from = k + actualDeleteCount - 1; - const to = k + itemCount - 1; + for (const k of $range(len - actualDeleteCount, start + 1, -1)) { + const from = k + actualDeleteCount; + const to = k + itemCount; - if (list[from]) { - list[to] = list[from]; + if (this[from - 1]) { + this[to - 1] = this[from - 1]; } else { - list[to] = undefined; + this[to - 1] = undefined; } } } - let j = actualStart; + let j = start + 1; for (const i of $range(3, actualArgumentCount)) { - list[j] = select(i, ...args)[0]; + this[j - 1] = args[i - 1]; j++; } - for (let k = list.length - 1; k >= len - actualDeleteCount + itemCount; k--) { - list[k] = undefined; + for (const k of $range(this.length, len - actualDeleteCount + itemCount + 1, -1)) { + this[k - 1] = undefined; } return out; diff --git a/src/lualib/ArrayToObject.ts b/src/lualib/ArrayToObject.ts index e3ed41f78..1f9e9778a 100644 --- a/src/lualib/ArrayToObject.ts +++ b/src/lualib/ArrayToObject.ts @@ -1,7 +1,7 @@ -export function __TS__ArrayToObject(this: void, array: T[]): Record { +export function __TS__ArrayToObject(this: T[]): Record { const object: Record = {}; - for (let i = 0; i < array.length; i += 1) { - object[i] = array[i]; + for (const i of $range(1, this.length)) { + object[i - 1] = this[i - 1]; } return object; } diff --git a/src/lualib/ArrayUnshift.ts b/src/lualib/ArrayUnshift.ts index c3dd6bdb4..d9ad686db 100644 --- a/src/lualib/ArrayUnshift.ts +++ b/src/lualib/ArrayUnshift.ts @@ -1,6 +1,12 @@ -export function __TS__ArrayUnshift(this: void, arr: T[], ...items: T[]): number { - for (let i = items.length - 1; i >= 0; --i) { - table.insert(arr, 1, items[i]); +export function __TS__ArrayUnshift(this: T[], ...items: T[]): number { + const length = items.length; + if (length === 0) return this.length; + + for (const i of $range(this.length, 1, -1)) { + this[i + length - 1] = this[i - 1]; } - return arr.length; + for (const i of $range(1, length)) { + this[i - 1] = items[i - 1]; + } + return this.length; } diff --git a/src/lualib/FunctionBind.ts b/src/lualib/FunctionBind.ts index a6cb2fac5..f36e9f6e5 100644 --- a/src/lualib/FunctionBind.ts +++ b/src/lualib/FunctionBind.ts @@ -1,13 +1,10 @@ export function __TS__FunctionBind( this: void, fn: (this: void, ...argArray: any[]) => any, - thisArg: any, ...boundArgs: any[] ): (...args: any[]) => any { return (...args: any[]) => { - for (let i = 0; i < boundArgs.length; ++i) { - table.insert(args, i + 1, boundArgs[i]); - } - return fn(thisArg, ...args); + args.unshift(...boundArgs); + return fn(...args); }; } diff --git a/src/lualib/ObjectAssign.ts b/src/lualib/ObjectAssign.ts index a351c030a..4fd4aff0d 100644 --- a/src/lualib/ObjectAssign.ts +++ b/src/lualib/ObjectAssign.ts @@ -1,15 +1,12 @@ // https://tc39.github.io/ecma262/#sec-object.assign // eslint-disable-next-line @typescript-eslint/ban-types -export function __TS__ObjectAssign(this: void, to: T, ...sources: object[]): T { - if (to === undefined) { - return to; - } - - for (const source of sources) { +export function __TS__ObjectAssign(this: void, target: T, ...sources: T[]): T { + for (const i of $range(1, sources.length)) { + const source = sources[i - 1]; for (const key in source) { - to[key] = source[key]; + target[key] = source[key]; } } - return to; + return target; } diff --git a/src/lualib/ObjectEntries.ts b/src/lualib/ObjectEntries.ts index 3059fbecf..5daeee1e0 100644 --- a/src/lualib/ObjectEntries.ts +++ b/src/lualib/ObjectEntries.ts @@ -1,7 +1,9 @@ export function __TS__ObjectEntries(this: void, obj: any): Array { const result = []; + let len = 0; for (const key in obj) { - result[result.length] = [key, obj[key]]; + len++; + result[len - 1] = [key, obj[key]]; } return result; } diff --git a/src/lualib/ObjectKeys.ts b/src/lualib/ObjectKeys.ts index d07f6c8d6..c7a0c0208 100644 --- a/src/lualib/ObjectKeys.ts +++ b/src/lualib/ObjectKeys.ts @@ -1,7 +1,9 @@ export function __TS__ObjectKeys(this: void, obj: any): Array { const result = []; + let len = 0; for (const key in obj) { - result[result.length] = key; + len++; + result[len - 1] = key; } return result; } diff --git a/src/lualib/ObjectValues.ts b/src/lualib/ObjectValues.ts index 02ab400cb..925a61ab3 100644 --- a/src/lualib/ObjectValues.ts +++ b/src/lualib/ObjectValues.ts @@ -1,7 +1,9 @@ export function __TS__ObjectValues(this: void, obj: any): Array { const result = []; + let len = 0; for (const key in obj) { - result[result.length] = obj[key]; + len++; + result[len - 1] = obj[key]; } return result; } diff --git a/src/lualib/Spread.ts b/src/lualib/Spread.ts index 46ec8d5ae..ffe745b6d 100644 --- a/src/lualib/Spread.ts +++ b/src/lualib/Spread.ts @@ -2,12 +2,14 @@ export function __TS__Spread(this: void, iterable: string | Iterable): Lua const arr = []; if (typeof iterable === "string") { // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < iterable.length; i += 1) { - arr[arr.length] = iterable[i]; + for (const i of $range(0, iterable.length - 1)) { + arr[i] = iterable[i]; } } else { + let len = 0; for (const item of iterable) { - arr[arr.length] = item; + len++; + arr[len - 1] = item; } } return $multi(...arr); diff --git a/src/lualib/StringConcat.ts b/src/lualib/StringConcat.ts deleted file mode 100644 index 473040f31..000000000 --- a/src/lualib/StringConcat.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function __TS__StringConcat(this: void, str1: string, ...args: string[]): string { - let out = str1; - for (const arg of args) { - out += arg; - } - return out; -} diff --git a/src/lualib/StringReplace.ts b/src/lualib/StringReplace.ts index f7de1da7a..9a2da017b 100644 --- a/src/lualib/StringReplace.ts +++ b/src/lualib/StringReplace.ts @@ -1,3 +1,4 @@ +const sub = string.sub; export function __TS__StringReplace( this: void, source: string, @@ -8,7 +9,6 @@ export function __TS__StringReplace( if (!startPos) { return source; } - const sub = string.sub; const before = sub(source, 1, startPos - 1); const replacement = typeof replaceValue === "string" ? replaceValue : replaceValue(searchValue, startPos - 1, source); diff --git a/src/lualib/StringReplaceAll.ts b/src/lualib/StringReplaceAll.ts index 6911f92d7..c110512e9 100644 --- a/src/lualib/StringReplaceAll.ts +++ b/src/lualib/StringReplaceAll.ts @@ -1,35 +1,36 @@ +const sub = string.sub; +const find = string.find; export function __TS__StringReplaceAll( this: void, source: string, searchValue: string, replaceValue: string | ((match: string, offset: number, string: string) => string) ): string { - let replacer: (match: string, offset: number, string: string) => string; if (typeof replaceValue === "string") { - replacer = () => replaceValue; - } else { - replacer = replaceValue; + const concat = table.concat(source.split(searchValue), replaceValue); + if (searchValue.length === 0) { + return replaceValue + concat + replaceValue; + } + return concat; } const parts: string[] = []; let partsIndex = 1; - const sub = string.sub; if (searchValue.length === 0) { - parts[0] = replacer("", 0, source); + parts[0] = replaceValue("", 0, source); partsIndex = 2; for (const i of $range(1, source.length)) { parts[partsIndex - 1] = sub(source, i, i); - parts[partsIndex] = replacer("", i, source); + parts[partsIndex] = replaceValue("", i, source); partsIndex += 2; } } else { - const find = string.find; let currentPos = 1; while (true) { const [startPos, endPos] = find(source, searchValue, currentPos, true); if (!startPos) break; parts[partsIndex - 1] = sub(source, currentPos, startPos - 1); - parts[partsIndex] = replacer(searchValue, startPos - 1, source); + parts[partsIndex] = replaceValue(searchValue, startPos - 1, source); partsIndex += 2; currentPos = endPos + 1; diff --git a/src/lualib/StringSplit.ts b/src/lualib/StringSplit.ts index cf2b6ae9d..1d2dd6610 100644 --- a/src/lualib/StringSplit.ts +++ b/src/lualib/StringSplit.ts @@ -1,3 +1,5 @@ +const sub = string.sub; +const find = string.find; export function __TS__StringSplit(this: void, source: string, separator?: string, limit?: number): string[] { if (limit === undefined) { limit = 4294967295; @@ -7,30 +9,28 @@ export function __TS__StringSplit(this: void, source: string, separator?: string return []; } - const out = []; - let index = 0; - let count = 0; + const result = []; + let resultIndex = 1; if (separator === undefined || separator === "") { - while (index < source.length - 1 && count < limit) { - out[count] = source[index]; - count++; - index++; + for (const i of $range(1, source.length)) { + result[resultIndex - 1] = sub(source, i, i); + resultIndex++; } } else { - const separatorLength = separator.length; - let nextIndex = source.indexOf(separator); - while (nextIndex >= 0 && count < limit) { - out[count] = source.substring(index, nextIndex); - count++; - index = nextIndex + separatorLength; - nextIndex = source.indexOf(separator, index); + let currentPos = 1; + while (resultIndex <= limit) { + const [startPos, endPos] = find(source, separator, currentPos, true); + if (!startPos) break; + result[resultIndex - 1] = sub(source, currentPos, startPos - 1); + resultIndex++; + currentPos = endPos + 1; } - } - if (count < limit) { - out[count] = source.substring(index); + if (resultIndex <= limit) { + result[resultIndex - 1] = sub(source, currentPos); + } } - return out; + return result; } diff --git a/src/transformation/builtins/array.ts b/src/transformation/builtins/array.ts index 7bbd4cf98..6b6fa4207 100644 --- a/src/transformation/builtins/array.ts +++ b/src/transformation/builtins/array.ts @@ -4,7 +4,8 @@ import { TransformationContext } from "../context"; import { unsupportedProperty } from "../utils/diagnostics"; import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { PropertyCallExpression, transformArguments, transformCallAndArguments } from "../visitors/call"; -import { isStringType, isNumberType } from "../utils/typescript"; +import { isStringType, isNumberType, findFirstNonOuterParent } from "../utils/typescript"; +import { moveToPrecedingTemp } from "../visitors/expression-list"; export function transformArrayConstructorCall( context: TransformationContext, @@ -23,10 +24,47 @@ export function transformArrayConstructorCall( } } +/** + * Optimized single element Array.push + * + * array[#array+1] = el + * return (#array + 1) + */ +function transformSingleElementArrayPush( + context: TransformationContext, + node: PropertyCallExpression, + caller: lua.Expression, + param: lua.Expression +): lua.Expression { + const expressionIsUsed = !ts.isExpressionStatement(findFirstNonOuterParent(node)); + + const arrayIdentifier = lua.isIdentifier(caller) ? caller : moveToPrecedingTemp(context, caller); + + // #array + 1 + let lengthExpression: lua.Expression = lua.createBinaryExpression( + lua.createUnaryExpression(arrayIdentifier, lua.SyntaxKind.LengthOperator), + lua.createNumericLiteral(1), + lua.SyntaxKind.AdditionOperator + ); + + if (expressionIsUsed) { + // store length in a temp + lengthExpression = moveToPrecedingTemp(context, lengthExpression); + } + + const pushStatement = lua.createAssignmentStatement( + lua.createTableIndexExpression(arrayIdentifier, lengthExpression), + param, + node + ); + context.addPrecedingStatements(pushStatement); + return expressionIsUsed ? lengthExpression : lua.createNilLiteral(); +} + export function transformArrayPrototypeCall( context: TransformationContext, node: PropertyCallExpression -): lua.CallExpression | undefined { +): lua.Expression | undefined { const expression = node.expression; const signature = context.checker.getResolvedSignature(node); const [caller, params] = transformCallAndArguments(context, expression.expression, node.arguments, signature); @@ -38,11 +76,19 @@ export function transformArrayPrototypeCall( case "entries": return transformLuaLibFunction(context, LuaLibFeature.ArrayEntries, node, caller); case "push": + if (node.arguments.length === 1 && !ts.isSpreadElement(node.arguments[0])) { + return transformSingleElementArrayPush(context, node, caller, params[0]); + } + return transformLuaLibFunction(context, LuaLibFeature.ArrayPush, node, caller, ...params); case "reverse": return transformLuaLibFunction(context, LuaLibFeature.ArrayReverse, node, caller); case "shift": - return transformLuaLibFunction(context, LuaLibFeature.ArrayShift, node, caller); + return lua.createCallExpression( + lua.createTableIndexExpression(lua.createIdentifier("table"), lua.createStringLiteral("remove")), + [caller, lua.createNumericLiteral(1)], + node + ); case "unshift": return transformLuaLibFunction(context, LuaLibFeature.ArrayUnshift, node, caller, ...params); case "sort": @@ -84,11 +130,14 @@ export function transformArrayPrototypeCall( const elementType = context.checker.getElementTypeOfArrayType(callerType); if (elementType && (isStringType(context, elementType) || isNumberType(context, elementType))) { const defaultSeparatorLiteral = lua.createStringLiteral(","); + const param = params[0]; const parameters = [ caller, node.arguments.length === 0 ? defaultSeparatorLiteral - : lua.createBinaryExpression(params[0], defaultSeparatorLiteral, lua.SyntaxKind.OrOperator), + : lua.isStringLiteral(param) + ? param + : lua.createBinaryExpression(param, defaultSeparatorLiteral, lua.SyntaxKind.OrOperator), ]; return lua.createCallExpression( diff --git a/src/transformation/builtins/string.ts b/src/transformation/builtins/string.ts index c71f855fc..d151d8d47 100644 --- a/src/transformation/builtins/string.ts +++ b/src/transformation/builtins/string.ts @@ -2,7 +2,7 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import { TransformationContext } from "../context"; import { unsupportedProperty } from "../utils/diagnostics"; -import { addToNumericExpression, createNaN, getNumberLiteralValue } from "../utils/lua-ast"; +import { addToNumericExpression, createNaN, getNumberLiteralValue, wrapInTable } from "../utils/lua-ast"; import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { PropertyCallExpression, transformArguments, transformCallAndArguments } from "../visitors/call"; @@ -30,7 +30,11 @@ export function transformStringPrototypeCall( case "replaceAll": return transformLuaLibFunction(context, LuaLibFeature.StringReplaceAll, node, caller, ...params); case "concat": - return transformLuaLibFunction(context, LuaLibFeature.StringConcat, node, caller, ...params); + return lua.createCallExpression( + lua.createTableIndexExpression(lua.createIdentifier("table"), lua.createStringLiteral("concat")), + [wrapInTable(caller, ...params)], + node + ); case "indexOf": { const stringExpression = createStringCall( diff --git a/src/transformation/utils/typescript/index.ts b/src/transformation/utils/typescript/index.ts index 1e2ffba44..f9c60f583 100644 --- a/src/transformation/utils/typescript/index.ts +++ b/src/transformation/utils/typescript/index.ts @@ -24,6 +24,14 @@ export function findFirstNodeAbove(node: ts.Node, callback: ( } } +export function findFirstNonOuterParent(node: ts.Node): ts.Node { + let current = node.parent; + while (ts.isOuterExpression(current)) { + current = current.parent; + } + return current; +} + export function getFirstDeclarationInFile(symbol: ts.Symbol, sourceFile: ts.SourceFile): ts.Declaration | undefined { const originalSourceFile = ts.getParseTreeNode(sourceFile) ?? sourceFile; const declarations = (symbol.getDeclarations() ?? []).filter(d => d.getSourceFile() === originalSourceFile); diff --git a/src/transformation/visitors/spread.ts b/src/transformation/visitors/spread.ts index 455f25fbf..1cfbe18ea 100644 --- a/src/transformation/visitors/spread.ts +++ b/src/transformation/visitors/spread.ts @@ -11,20 +11,13 @@ import { isFunctionScopeWithDefinition, ScopeType, } from "../utils/scope"; -import { isArrayType } from "../utils/typescript"; +import { isArrayType, findFirstNonOuterParent } from "../utils/typescript"; import { isMultiReturnCall } from "./language-extensions/multi"; import { annotationRemoved } from "../utils/diagnostics"; import { isGlobalVarargConstant } from "./language-extensions/vararg"; -function skipOuterExpressionParents(node: ts.Node) { - while (ts.isOuterExpression(node)) { - node = node.parent; - } - return node; -} - export function isOptimizedVarArgSpread(context: TransformationContext, symbol: ts.Symbol, identifier: ts.Identifier) { - if (!ts.isSpreadElement(skipOuterExpressionParents(identifier.parent))) { + if (!ts.isSpreadElement(findFirstNonOuterParent(identifier))) { return false; } diff --git a/test/unit/__snapshots__/switch.spec.ts.snap b/test/unit/__snapshots__/switch.spec.ts.snap index a640878c4..ef5504938 100644 --- a/test/unit/__snapshots__/switch.spec.ts.snap +++ b/test/unit/__snapshots__/switch.spec.ts.snap @@ -1,16 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`switch empty fallthrough to default (0) 1`] = ` -"local ____lualib = require(\\"lualib_bundle\\") -local __TS__ArrayPush = ____lualib.__TS__ArrayPush -local ____exports = {} +"local ____exports = {} function ____exports.__main(self) local out = {} repeat local ____switch3 = 0 local ____cond3 = ____switch3 == 1 do - __TS__ArrayPush(out, \\"default\\") + out[#out + 1] = \\"default\\" end until true return out @@ -19,16 +17,14 @@ return ____exports" `; exports[`switch empty fallthrough to default (1) 1`] = ` -"local ____lualib = require(\\"lualib_bundle\\") -local __TS__ArrayPush = ____lualib.__TS__ArrayPush -local ____exports = {} +"local ____exports = {} function ____exports.__main(self) local out = {} repeat local ____switch3 = 1 local ____cond3 = ____switch3 == 1 do - __TS__ArrayPush(out, \\"default\\") + out[#out + 1] = \\"default\\" end until true return out @@ -113,9 +109,7 @@ return ____exports" `; exports[`switch produces optimal output 1`] = ` -"local ____lualib = require(\\"lualib_bundle\\") -local __TS__ArrayPush = ____lualib.__TS__ArrayPush -local ____exports = {} +"local ____exports = {} function ____exports.__main(self) local x = 0 local out = {} @@ -123,13 +117,13 @@ function ____exports.__main(self) local ____switch3 = 0 local ____cond3 = ____switch3 == 0 or ____switch3 == 1 or ____switch3 == 2 if ____cond3 then - __TS__ArrayPush(out, \\"0,1,2\\") + out[#out + 1] = \\"0,1,2\\" break end ____cond3 = ____cond3 or ____switch3 == 3 if ____cond3 then do - __TS__ArrayPush(out, \\"3\\") + out[#out + 1] = \\"3\\" break end end @@ -139,20 +133,14 @@ function ____exports.__main(self) end do x = x + 1 - __TS__ArrayPush( - out, - \\"default = \\" .. tostring(x) - ) + out[#out + 1] = \\"default = \\" .. tostring(x) do - __TS__ArrayPush(out, \\"3\\") + out[#out + 1] = \\"3\\" break end end until true - __TS__ArrayPush( - out, - tostring(x) - ) + out[#out + 1] = tostring(x) return out end return ____exports" diff --git a/test/unit/builtins/array.spec.ts b/test/unit/builtins/array.spec.ts index eaeddf08e..887128649 100644 --- a/test/unit/builtins/array.spec.ts +++ b/test/unit/builtins/array.spec.ts @@ -472,10 +472,10 @@ test.each([ util.testExpression`${util.formatCode(array)}.indexOf(${util.formatCode(...args)})`.expectToMatchJsResult(); }); -test.each([{ args: [1] }, { args: [1, 2, 3] }])("array.push (%p)", ({ args }) => { +test.each([{ args: "1" }, { args: "1, 2, 3" }, { args: "...[1, 2, 3]" }])("array.push (%p)", ({ args }) => { util.testFunction` const array = [0]; - const value = array.push(${util.formatCode(...args)}); + const value = array.push(${args}); return { array, value }; `.expectToMatchJsResult(); }); diff --git a/test/unit/builtins/loading.spec.ts b/test/unit/builtins/loading.spec.ts index a5da97772..ac76de5a1 100644 --- a/test/unit/builtins/loading.spec.ts +++ b/test/unit/builtins/loading.spec.ts @@ -4,14 +4,14 @@ import * as util from "../../util"; describe("luaLibImport", () => { test("inline", () => { - util.testExpression`[0].push(1)` + util.testExpression`[0].indexOf(1)` .setOptions({ luaLibImport: tstl.LuaLibImportKind.Inline }) .tap(builder => expect(builder.getMainLuaCodeChunk()).not.toContain('require("lualib_bundle")')) .expectToMatchJsResult(); }); test("require", () => { - util.testExpression`[0].push(1)` + util.testExpression`[0].indexOf(1)` .setOptions({ luaLibImport: tstl.LuaLibImportKind.Require }) .tap(builder => expect(builder.getMainLuaCodeChunk()).toContain('require("lualib_bundle")')) .expectToMatchJsResult(); From 0ae4d05ba6926c9b0befdbd60908983a3668d75c Mon Sep 17 00:00:00 2001 From: GlassBricks <24237065+GlassBricks@users.noreply.github.com> Date: Thu, 24 Feb 2022 17:33:46 -0800 Subject: [PATCH 3/6] Introduce NodeFlags --- src/LuaAST.ts | 24 +++++++++---------- src/LuaPrinter.ts | 5 +--- src/transformation/visitors/class/index.ts | 2 +- .../visitors/class/members/accessors.ts | 2 +- .../visitors/class/members/constructor.ts | 2 +- src/transformation/visitors/function.ts | 8 +++---- 6 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/LuaAST.ts b/src/LuaAST.ts index ee5c9d329..25635f43a 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -125,6 +125,12 @@ export type Operator = UnaryOperator | BinaryOperator; export type SymbolId = number & { _symbolIdBrand: any }; +export enum NodeFlags { + None = 0, + Inline = 1 << 0, // Keep function body on same line + Declaration = 1 << 1, // Prefer declaration syntax `function foo()` over assignment syntax `foo = function()` +} + export interface TextRange { line?: number; column?: number; @@ -132,18 +138,19 @@ export interface TextRange { export interface Node extends TextRange { kind: SyntaxKind; + flags: NodeFlags; } export function createNode(kind: SyntaxKind, tsOriginal?: ts.Node): Node { if (tsOriginal === undefined) { - return { kind }; + return { kind, flags: NodeFlags.None }; } const sourcePosition = getSourcePosition(tsOriginal); if (sourcePosition) { - return { kind, line: sourcePosition.line, column: sourcePosition.column }; + return { kind, line: sourcePosition.line, column: sourcePosition.column, flags: NodeFlags.None }; } else { - return { kind }; + return { kind, flags: NodeFlags.None }; } } @@ -578,18 +585,11 @@ export function isLiteral( ); } -export enum FunctionExpressionFlags { - None = 1 << 0, - Inline = 1 << 1, // Keep function body on same line - Declaration = 1 << 2, // Prefer declaration syntax `function foo()` over assignment syntax `foo = function()` -} - export interface FunctionExpression extends Expression { kind: SyntaxKind.FunctionExpression; params?: Identifier[]; dots?: DotsLiteral; body: Block; - flags: FunctionExpressionFlags; } export function isFunctionExpression(node: Node): node is FunctionExpression { @@ -600,7 +600,7 @@ export function createFunctionExpression( body: Block, params?: Identifier[], dots?: DotsLiteral, - flags = FunctionExpressionFlags.None, + flags = NodeFlags.None, tsOriginal?: ts.Node ): FunctionExpression { const expression = createNode(SyntaxKind.FunctionExpression, tsOriginal) as FunctionExpression; @@ -819,6 +819,6 @@ export function isInlineFunctionExpression(expression: FunctionExpression): expr expression.body.statements?.length === 1 && isReturnStatement(expression.body.statements[0]) && expression.body.statements[0].expressions !== undefined && - (expression.flags & FunctionExpressionFlags.Inline) !== 0 + (expression.flags & NodeFlags.Inline) !== 0 ); } diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index cc8a6fa46..53754655e 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -433,10 +433,7 @@ export class LuaPrinter { chunks.push(this.indent()); - if ( - lua.isFunctionDefinition(statement) && - (statement.right[0].flags & lua.FunctionExpressionFlags.Declaration) !== 0 - ) { + if (lua.isFunctionDefinition(statement) && (statement.right[0].flags & lua.NodeFlags.Declaration) !== 0) { // Use `function foo()` instead of `foo = function()` const name = this.printExpression(statement.left[0]); if (isValidLuaFunctionDeclarationName(name.toString(), this.options)) { diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index 5257c2db6..80b5f7536 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -155,7 +155,7 @@ function transformClassLikeDeclaration( lua.createBlock(constructorBody), [createSelfIdentifier()], lua.createDotsLiteral(), - lua.FunctionExpressionFlags.Declaration + lua.NodeFlags.Declaration ); result.push( lua.createAssignmentStatement(createConstructorName(localClassName), constructorFunction, classDeclaration) diff --git a/src/transformation/visitors/class/members/accessors.ts b/src/transformation/visitors/class/members/accessors.ts index f774b9645..fe34a3d8a 100644 --- a/src/transformation/visitors/class/members/accessors.ts +++ b/src/transformation/visitors/class/members/accessors.ts @@ -11,7 +11,7 @@ import { createPrototypeName } from "./constructor"; function transformAccessor(context: TransformationContext, node: ts.AccessorDeclaration): lua.FunctionExpression { const [params, dot, restParam] = transformParameters(context, node.parameters, createSelfIdentifier()); const body = node.body ? transformFunctionBody(context, node.parameters, node.body, restParam)[0] : []; - return lua.createFunctionExpression(lua.createBlock(body), params, dot, lua.FunctionExpressionFlags.Declaration); + return lua.createFunctionExpression(lua.createBlock(body), params, dot, lua.NodeFlags.Declaration); } export function transformAccessorDeclarations( diff --git a/src/transformation/visitors/class/members/constructor.ts b/src/transformation/visitors/class/members/constructor.ts index 11766d2a8..22ab14feb 100644 --- a/src/transformation/visitors/class/members/constructor.ts +++ b/src/transformation/visitors/class/members/constructor.ts @@ -90,7 +90,7 @@ export function transformConstructorDeclaration( return lua.createAssignmentStatement( createConstructorName(className), - lua.createFunctionExpression(block, params, dotsLiteral, lua.FunctionExpressionFlags.Declaration), + lua.createFunctionExpression(block, params, dotsLiteral, lua.NodeFlags.Declaration), constructorWasGenerated ? classDeclaration : statement ); } diff --git a/src/transformation/visitors/function.ts b/src/transformation/visitors/function.ts index 9ced7089f..0b432ae84 100644 --- a/src/transformation/visitors/function.ts +++ b/src/transformation/visitors/function.ts @@ -66,7 +66,7 @@ export function createCallableTable(functionExpression: lua.Expression): lua.Exp ]), [lua.createAnonymousIdentifier()], lua.createDotsLiteral(), - lua.FunctionExpressionFlags.Inline + lua.NodeFlags.Inline ); } return lua.createCallExpression(lua.createIdentifier("setmetatable"), [ @@ -231,10 +231,10 @@ export function transformFunctionToExpression( } } - let flags = lua.FunctionExpressionFlags.None; - if (!ts.isBlock(node.body)) flags |= lua.FunctionExpressionFlags.Inline; + let flags = lua.NodeFlags.None; + if (!ts.isBlock(node.body)) flags |= lua.NodeFlags.Inline; if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) { - flags |= lua.FunctionExpressionFlags.Declaration; + flags |= lua.NodeFlags.Declaration; } const [paramNames, dotsLiteral, spreadIdentifier] = transformParameters(context, node.parameters, functionContext); From 4a68d9ceb690767d23b13dc314b108ca7ca707b1 Mon Sep 17 00:00:00 2001 From: GlassBricks <24237065+GlassBricks@users.noreply.github.com> Date: Thu, 24 Feb 2022 17:59:39 -0800 Subject: [PATCH 4/6] Optimize array push array --- src/LuaAST.ts | 6 ++++++ src/LuaLib.ts | 1 + src/lualib/ArrayPushArray.ts | 8 ++++++++ src/transformation/builtins/array.ts | 17 +++++++++++++++-- src/transformation/utils/lua-ast.ts | 6 +++++- test/unit/builtins/array.spec.ts | 25 +++++++++++++++++++++++-- 6 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 src/lualib/ArrayPushArray.ts diff --git a/src/LuaAST.ts b/src/LuaAST.ts index 25635f43a..472831451 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -129,6 +129,7 @@ export enum NodeFlags { None = 0, Inline = 1 << 0, // Keep function body on same line Declaration = 1 << 1, // Prefer declaration syntax `function foo()` over assignment syntax `foo = function()` + TableUnpackCall = 1 << 2, // This is a table.unpack call } export interface TextRange { @@ -197,6 +198,11 @@ export function getOriginalPos(node: Node): TextRange { return { line: node.line, column: node.column }; } +export function setNodeFlags(node: T, flags: NodeFlags): T { + node.flags = flags; + return node; +} + export interface File extends Node { kind: SyntaxKind.File; statements: Statement[]; diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 2584f0826..9501fd620 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -16,6 +16,7 @@ export enum LuaLibFeature { ArrayJoin = "ArrayJoin", ArrayMap = "ArrayMap", ArrayPush = "ArrayPush", + ArrayPushArray = "ArrayPushArray", ArrayReduce = "ArrayReduce", ArrayReduceRight = "ArrayReduceRight", ArrayReverse = "ArrayReverse", diff --git a/src/lualib/ArrayPushArray.ts b/src/lualib/ArrayPushArray.ts new file mode 100644 index 000000000..8ea9b6916 --- /dev/null +++ b/src/lualib/ArrayPushArray.ts @@ -0,0 +1,8 @@ +export function __TS__ArrayPushArray(this: T[], items: T[]): number { + let len = this.length; + for (const i of $range(1, items.length)) { + len++; + this[len - 1] = items[i - 1]; + } + return len; +} diff --git a/src/transformation/builtins/array.ts b/src/transformation/builtins/array.ts index 6b6fa4207..23221f306 100644 --- a/src/transformation/builtins/array.ts +++ b/src/transformation/builtins/array.ts @@ -6,6 +6,7 @@ import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { PropertyCallExpression, transformArguments, transformCallAndArguments } from "../visitors/call"; import { isStringType, isNumberType, findFirstNonOuterParent } from "../utils/typescript"; import { moveToPrecedingTemp } from "../visitors/expression-list"; +import { isUnpackCall } from "../utils/lua-ast"; export function transformArrayConstructorCall( context: TransformationContext, @@ -76,8 +77,20 @@ export function transformArrayPrototypeCall( case "entries": return transformLuaLibFunction(context, LuaLibFeature.ArrayEntries, node, caller); case "push": - if (node.arguments.length === 1 && !ts.isSpreadElement(node.arguments[0])) { - return transformSingleElementArrayPush(context, node, caller, params[0]); + if (node.arguments.length === 1) { + const param = params[0]; + if (isUnpackCall(param)) { + return transformLuaLibFunction( + context, + LuaLibFeature.ArrayPushArray, + node, + caller, + (param as lua.CallExpression).params[0] + ); + } + if (!lua.isDotsLiteral(param)) { + return transformSingleElementArrayPush(context, node, caller, param); + } } return transformLuaLibFunction(context, LuaLibFeature.ArrayPush, node, caller, ...params); diff --git a/src/transformation/utils/lua-ast.ts b/src/transformation/utils/lua-ast.ts index 9508479ce..b894dba63 100644 --- a/src/transformation/utils/lua-ast.ts +++ b/src/transformation/utils/lua-ast.ts @@ -76,7 +76,11 @@ export function createUnpackCall( ? lua.createIdentifier("unpack") : lua.createTableIndexExpression(lua.createIdentifier("table"), lua.createStringLiteral("unpack")); - return lua.createCallExpression(unpack, [expression], tsOriginal); + return lua.setNodeFlags(lua.createCallExpression(unpack, [expression], tsOriginal), lua.NodeFlags.TableUnpackCall); +} + +export function isUnpackCall(node: lua.Node): boolean { + return lua.isCallExpression(node) && (node.flags & lua.NodeFlags.TableUnpackCall) !== 0; } export function wrapInTable(...expressions: lua.Expression[]): lua.TableExpression { diff --git a/test/unit/builtins/array.spec.ts b/test/unit/builtins/array.spec.ts index 887128649..697418b31 100644 --- a/test/unit/builtins/array.spec.ts +++ b/test/unit/builtins/array.spec.ts @@ -472,12 +472,33 @@ test.each([ util.testExpression`${util.formatCode(array)}.indexOf(${util.formatCode(...args)})`.expectToMatchJsResult(); }); -test.each([{ args: "1" }, { args: "1, 2, 3" }, { args: "...[1, 2, 3]" }])("array.push (%p)", ({ args }) => { +test.each([ + { args: "1" }, + { args: "1, 2, 3" }, + { args: "...[1, 2, 3]" }, + { args: "1, 2, ...[3, 4]" }, + { args: "1, 2, ...[3, 4], 5" }, +])("array.push (%p)", ({ args }) => { util.testFunction` const array = [0]; const value = array.push(${args}); return { array, value }; - `.expectToMatchJsResult(); + ` + .debug() + .expectToMatchJsResult(); +}); + +test("array.push(...)", () => { + util.testFunction` + const array = [0]; + function push(...args: any[]) { + return array.push(...args); + } + const value = push(1, 2, 3); + return { array, value }; + ` + .debug() + .expectToMatchJsResult(); }); test.each([ From 995b6b3fbf63f8d1def24dca0815ae5b6d2bd79e Mon Sep 17 00:00:00 2001 From: GlassBricks <24237065+GlassBricks@users.noreply.github.com> Date: Thu, 17 Mar 2022 14:09:52 -0700 Subject: [PATCH 5/6] Remove `debug()` from tests --- test/unit/builtins/array.spec.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/unit/builtins/array.spec.ts b/test/unit/builtins/array.spec.ts index 697418b31..10abeba18 100644 --- a/test/unit/builtins/array.spec.ts +++ b/test/unit/builtins/array.spec.ts @@ -483,9 +483,7 @@ test.each([ const array = [0]; const value = array.push(${args}); return { array, value }; - ` - .debug() - .expectToMatchJsResult(); + `.expectToMatchJsResult(); }); test("array.push(...)", () => { @@ -496,9 +494,7 @@ test("array.push(...)", () => { } const value = push(1, 2, 3); return { array, value }; - ` - .debug() - .expectToMatchJsResult(); + `.expectToMatchJsResult(); }); test.each([ From d34c4fb1aeff42835f8571ca9c6a7a03d4f11164 Mon Sep 17 00:00:00 2001 From: GlassBricks <24237065+GlassBricks@users.noreply.github.com> Date: Sat, 2 Apr 2022 10:57:47 -0700 Subject: [PATCH 6/6] PR feedback --- src/lualib/ArraySplice.ts | 2 +- src/lualib/ArrayUnshift.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lualib/ArraySplice.ts b/src/lualib/ArraySplice.ts index 82900759d..dc69e16fb 100644 --- a/src/lualib/ArraySplice.ts +++ b/src/lualib/ArraySplice.ts @@ -29,7 +29,7 @@ export function __TS__ArraySplice(this: T[], ...args: any[]): T[] { // ECMA-spec line 6: if number of actual arguments is 1 actualDeleteCount = len - start; } else { - actualDeleteCount = deleteCount || 0; + actualDeleteCount = deleteCount ?? 0; if (actualDeleteCount < 0) { actualDeleteCount = 0; } diff --git a/src/lualib/ArrayUnshift.ts b/src/lualib/ArrayUnshift.ts index d9ad686db..6c628c625 100644 --- a/src/lualib/ArrayUnshift.ts +++ b/src/lualib/ArrayUnshift.ts @@ -1,11 +1,11 @@ export function __TS__ArrayUnshift(this: T[], ...items: T[]): number { - const length = items.length; - if (length === 0) return this.length; + const numItemsToInsert = items.length; + if (numItemsToInsert === 0) return this.length; for (const i of $range(this.length, 1, -1)) { - this[i + length - 1] = this[i - 1]; + this[i + numItemsToInsert - 1] = this[i - 1]; } - for (const i of $range(1, length)) { + for (const i of $range(1, numItemsToInsert)) { this[i - 1] = items[i - 1]; } return this.length;