From 2f7f6ee8eb5553c4dec8e0745b0111513de9ad20 Mon Sep 17 00:00:00 2001 From: Lorenz Junglas Date: Sat, 29 Jul 2023 10:14:29 +0200 Subject: [PATCH 1/2] Modified methodExtensionKinds to be auto generated from string enum as it can be easily forgotten to add new extensions here. --- .../utils/language-extensions.ts | 28 ++----------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/src/transformation/utils/language-extensions.ts b/src/transformation/utils/language-extensions.ts index 108c6eef1..eba63556a 100644 --- a/src/transformation/utils/language-extensions.ts +++ b/src/transformation/utils/language-extensions.ts @@ -122,31 +122,9 @@ export function getIterableExtensionKindForNode( return getIterableExtensionTypeForType(context, type); } -export const methodExtensionKinds: ReadonlySet = new Set([ - ExtensionKind.AdditionOperatorMethodType, - ExtensionKind.SubtractionOperatorMethodType, - ExtensionKind.MultiplicationOperatorMethodType, - ExtensionKind.DivisionOperatorMethodType, - ExtensionKind.ModuloOperatorMethodType, - ExtensionKind.PowerOperatorMethodType, - ExtensionKind.FloorDivisionOperatorMethodType, - ExtensionKind.BitwiseAndOperatorMethodType, - ExtensionKind.BitwiseOrOperatorMethodType, - ExtensionKind.BitwiseExclusiveOrOperatorMethodType, - ExtensionKind.BitwiseLeftShiftOperatorMethodType, - ExtensionKind.BitwiseRightShiftOperatorMethodType, - ExtensionKind.ConcatOperatorMethodType, - ExtensionKind.LessThanOperatorMethodType, - ExtensionKind.GreaterThanOperatorMethodType, - ExtensionKind.NegationOperatorMethodType, - ExtensionKind.BitwiseNotOperatorMethodType, - ExtensionKind.LengthOperatorMethodType, - ExtensionKind.TableDeleteMethodType, - ExtensionKind.TableGetMethodType, - ExtensionKind.TableHasMethodType, - ExtensionKind.TableSetMethodType, - ExtensionKind.TableAddKeyMethodType, -]); +export const methodExtensionKinds: ReadonlySet = new Set( + Object.values(ExtensionKind).filter(key => key.endsWith("Method")) +); export function getNaryCallExtensionArgs( context: TransformationContext, From 369f083448f6cb501ad0d8384362cc94f1166df6 Mon Sep 17 00:00:00 2001 From: Lorenz Junglas Date: Sat, 29 Jul 2023 10:18:16 +0200 Subject: [PATCH 2/2] LuaTableIsEmpty language extensions Added LuaTableIsEmpty & LuaTableIsEmptyMethod and added an isEmpty method to LuaTable, LuaMap & LuaSet Refactored existing tests a bit. Usage of extensions inside expressions is now tested in a single suite. --- language-extensions/index.d.ts | 19 +++ .../utils/language-extensions.ts | 2 + .../visitors/language-extensions/table.ts | 23 ++++ test/unit/language-extensions/table.spec.ts | 115 ++++++++++++------ 4 files changed, 125 insertions(+), 34 deletions(-) diff --git a/language-extensions/index.d.ts b/language-extensions/index.d.ts index 07b5cb335..1442e5aa3 100644 --- a/language-extensions/index.d.ts +++ b/language-extensions/index.d.ts @@ -562,6 +562,20 @@ declare type LuaTableDelete = ( declare type LuaTableDeleteMethod = ((key: TKey) => boolean) & LuaExtension<"TableDeleteMethod">; +/** + * Calls to functions with this type are translated to `next(myTable) == nil`. + * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions + * + * @param TTable The type to access as a Lua table. + */ +declare type LuaTableIsEmpty = ((table: TTable) => boolean) & LuaExtension<"TableIsEmpty">; + +/** + * Calls to methods with this type are translated to `next(myTable) == nil`, where `table` is the object with the method. + * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions + */ +declare type LuaTableIsEmptyMethod = (() => boolean) & LuaExtension<"TableIsEmptyMethod">; + /** * A convenience type for working directly with a Lua table. * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions @@ -575,6 +589,7 @@ declare interface LuaTable ext set: LuaTableSetMethod; has: LuaTableHasMethod; delete: LuaTableDeleteMethod; + isEmpty: LuaTableIsEmptyMethod; } /** @@ -612,6 +627,7 @@ declare interface LuaMap extends LuaPa set: LuaTableSetMethod; has: LuaTableHasMethod; delete: LuaTableDeleteMethod; + isEmpty: LuaTableIsEmptyMethod; } /** @@ -633,6 +649,7 @@ declare const LuaMap: (new () => LuaMa declare interface ReadonlyLuaMap extends LuaPairsIterable { get: LuaTableGetMethod; has: LuaTableHasMethod; + isEmpty: LuaTableIsEmptyMethod; } /** @@ -645,6 +662,7 @@ declare interface LuaSet extends LuaPairsKeyIte add: LuaTableAddKeyMethod; has: LuaTableHasMethod; delete: LuaTableDeleteMethod; + isEmpty: LuaTableIsEmptyMethod; } /** @@ -662,6 +680,7 @@ declare const LuaSet: (new () => LuaSet) & L */ declare interface ReadonlyLuaSet extends LuaPairsKeyIterable { has: LuaTableHasMethod; + isEmpty: LuaTableIsEmptyMethod; } interface ObjectConstructor { diff --git a/src/transformation/utils/language-extensions.ts b/src/transformation/utils/language-extensions.ts index eba63556a..6da38837a 100644 --- a/src/transformation/utils/language-extensions.ts +++ b/src/transformation/utils/language-extensions.ts @@ -53,6 +53,8 @@ export enum ExtensionKind { TableSetMethodType = "TableSetMethod", TableAddKeyType = "TableAddKey", TableAddKeyMethodType = "TableAddKeyMethod", + TableIsEmptyType = "TableIsEmpty", + TableIsEmptyMethodType = "TableIsEmptyMethod", } const extensionValues: Set = new Set(Object.values(ExtensionKind)); diff --git a/src/transformation/visitors/language-extensions/table.ts b/src/transformation/visitors/language-extensions/table.ts index d3aaedc5d..e42e3f8c7 100644 --- a/src/transformation/visitors/language-extensions/table.ts +++ b/src/transformation/visitors/language-extensions/table.ts @@ -6,6 +6,7 @@ import { getBinaryCallExtensionArgs, getExtensionKindForNode, getNaryCallExtensionArgs, + getUnaryCallExtensionArg, } from "../../utils/language-extensions"; import { transformOrderedExpressions } from "../expression-list"; import { LanguageExtensionCallTransformerMap } from "./call-extension"; @@ -27,6 +28,8 @@ export const tableExtensionTransformers: LanguageExtensionCallTransformerMap = { [ExtensionKind.TableSetMethodType]: transformTableSetExpression, [ExtensionKind.TableAddKeyType]: transformTableAddKeyExpression, [ExtensionKind.TableAddKeyMethodType]: transformTableAddKeyExpression, + [ExtensionKind.TableIsEmptyType]: transformTableIsEmptyExpression, + [ExtensionKind.TableIsEmptyMethodType]: transformTableIsEmptyExpression, }; function transformTableDeleteExpression( @@ -120,3 +123,23 @@ function transformTableAddKeyExpression( ); return lua.createNilLiteral(); } + +function transformTableIsEmptyExpression( + context: TransformationContext, + node: ts.CallExpression, + extensionKind: ExtensionKind +): lua.Expression { + const args = getUnaryCallExtensionArg(context, node, extensionKind); + if (!args) { + return lua.createNilLiteral(); + } + + const table = context.transformExpression(args); + // next(arg0) == nil + return lua.createBinaryExpression( + lua.createCallExpression(lua.createIdentifier("next"), [table], node), + lua.createNilLiteral(), + lua.SyntaxKind.EqualityOperator, + node + ); +} diff --git a/test/unit/language-extensions/table.spec.ts b/test/unit/language-extensions/table.spec.ts index b833de22b..40e46d61e 100644 --- a/test/unit/language-extensions/table.spec.ts +++ b/test/unit/language-extensions/table.spec.ts @@ -221,7 +221,7 @@ describe("LuaTableDelete extension", () => { .expectToEqual({ table: { baz: "baz" } }); }); - test("LuaTableHasMethod method", () => { + test("LuaTableDeleteMethod method", () => { util.testModule` interface TableWithDelete { delete: LuaTableDeleteMethod; @@ -235,33 +235,6 @@ describe("LuaTableDelete extension", () => { .withLanguageExtensions() .expectToEqual({ table: { bar: 12 } }); }); - - test.each([ - ["LuaTableDelete<{}, string>", 'func({}, "foo")', true], - ["LuaTableDelete<{}, string>", '"truthy" && func({}, "foo")', true], - ["LuaTableSet<{}, string, number>", 'func({}, "foo", 3)', undefined], - ])("Table functions used as expression", (funcType, expression, value) => { - util.testModule` - declare const func: ${funcType} - export const result = ${expression} - ` - .withLanguageExtensions() - .setReturnExport("result") - .expectToEqual(value); - }); - - test.each([ - ["LuaTableDeleteMethod", 'tbl.func("foo")', true], - ["LuaTableSetMethod", 'tbl.func("foo", 3)', undefined], - ])("Table methods used as expression", (funcType, expression, value) => { - util.testModule` - const tbl = {} as { func: ${funcType} } - export const result = ${expression} - ` - .withLanguageExtensions() - .setReturnExport("result") - .expectToEqual(value); - }); }); describe("LuaTableAddKey extension", () => { @@ -298,11 +271,67 @@ describe("LuaTableAddKey extension", () => { .withLanguageExtensions() .expectToEqual({ table: { bar: true } }); }); +}); + +describe("LuaIsEmpty extension", () => { + test("LuaIsEmpty standalone function", () => { + util.testModule` + declare const isTableEmpty: LuaTableIsEmpty<{}>; + + const table = { foo: "bar", baz: "baz" }; + const emptyTable = {}; + + export const result = [isTableEmpty(table), isTableEmpty(emptyTable)]; + ` + .withLanguageExtensions() + .expectToEqual({ result: [false, true] }); + }); + + test("LuaIsEmpty namespace function", () => { + util.testModule` + declare namespace Table { + export const isTableEmpty: LuaTableIsEmpty<{}>; + } + + const table = { foo: "bar", baz: "baz" }; + const emptyTable = {}; + + export const result = [Table.isTableEmpty(table), Table.isTableEmpty(emptyTable)]; + ` + .withLanguageExtensions() + .expectToEqual({ result: [false, true] }); + }); + test("LuaTableIsEmptyMethod method", () => { + util.testModule` + interface TableWithIsEmpty { + isEmpty: LuaTableIsEmptyMethod; + set: LuaTableSetMethod; + } + const table = {} as TableWithIsEmpty; + table.set("foo", 42); + table.set("bar", 12); + + const emptyTable = {} as TableWithIsEmpty; + + export const result = [table.isEmpty(), emptyTable.isEmpty()]; + ` + .withLanguageExtensions() + .expectToEqual({ result: [false, true] }); + }); +}); + +describe("Table extensions use as expression", () => { test.each([ ["LuaTableAddKey<{}, string>", 'func({}, "foo")', undefined], ["LuaTableAddKey<{}, string>", '"truthy" && func({}, "foo")', undefined], - ])("Table functions used as expression", (funcType, expression, value) => { + ["LuaTableDelete<{}, string>", 'func({}, "foo")', true], + ["LuaTableDelete<{}, string>", '"truthy" && func({}, "foo")', true], + ["LuaTableSet<{}, string, number>", 'func({}, "foo", 3)', undefined], + ["LuaTableIsEmpty<{}>", "func({})", true], + ["LuaTableIsEmpty<{}>", 'func({ foo: "bar", baz: "baz" })', false], + ["LuaTableIsEmpty<{}>", '"truthy" && func({})', true], + ])("functions used as expression", (funcType, expression, value) => { util.testModule` declare const func: ${funcType} export const result = ${expression} @@ -312,14 +341,19 @@ describe("LuaTableAddKey extension", () => { .expectToEqual(value); }); - test("Method used as expression", () => { + test.each([ + ["LuaTableDeleteMethod", 'tbl.func("foo")', true], + ["LuaTableSetMethod", 'tbl.func("foo", 3)', undefined], + ["LuaTableAddKeyMethod", 'tbl.func("foo")', undefined], + ["LuaTableIsEmpty<{}>", "tbl.func({})", true], + ])("methods used as expression", (funcType, expression, value) => { util.testModule` - const tbl = {} as { func: LuaTableAddKeyMethod } - export const result = tbl.func("foo") + const tbl = {} as { func: ${funcType} } + export const result = ${expression} ` .withLanguageExtensions() .setReturnExport("result") - .expectToEqual(undefined); + .expectToEqual(value); }); }); @@ -415,9 +449,22 @@ describe("LuaTable extension interface", () => { .expectToEqual({ baz: 5 }); }); + test("table isEmpty", () => { + util.testFunction` + const tbl = new LuaTable(); + tbl.set("foo", 1); + + const emptyTbl = new LuaTable(); + + return [tbl.isEmpty(), emptyTbl.isEmpty()]; + ` + .withLanguageExtensions() + .expectToEqual([false, true]); + }); + test("table add", () => { util.testFunction` - const tbl = new LuaSet() + const tbl = new LuaSet(); tbl.add("foo"); return tbl `