Skip to content

Commit 13e2856

Browse files
authored
Lua table is empty (#1470)
* Modified methodExtensionKinds to be auto generated from string enum as it can be easily forgotten to add new extensions here. * 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.
1 parent ff310f1 commit 13e2856

File tree

4 files changed

+128
-59
lines changed

4 files changed

+128
-59
lines changed

language-extensions/index.d.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,20 @@ declare type LuaTableDelete<TTable extends AnyTable, TKey extends AnyNotNil> = (
562562
declare type LuaTableDeleteMethod<TKey extends AnyNotNil> = ((key: TKey) => boolean) &
563563
LuaExtension<"TableDeleteMethod">;
564564

565+
/**
566+
* Calls to functions with this type are translated to `next(myTable) == nil`.
567+
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
568+
*
569+
* @param TTable The type to access as a Lua table.
570+
*/
571+
declare type LuaTableIsEmpty<TTable extends AnyTable> = ((table: TTable) => boolean) & LuaExtension<"TableIsEmpty">;
572+
573+
/**
574+
* Calls to methods with this type are translated to `next(myTable) == nil`, where `table` is the object with the method.
575+
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
576+
*/
577+
declare type LuaTableIsEmptyMethod = (() => boolean) & LuaExtension<"TableIsEmptyMethod">;
578+
565579
/**
566580
* A convenience type for working directly with a Lua table.
567581
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
@@ -575,6 +589,7 @@ declare interface LuaTable<TKey extends AnyNotNil = AnyNotNil, TValue = any> ext
575589
set: LuaTableSetMethod<TKey, TValue>;
576590
has: LuaTableHasMethod<TKey>;
577591
delete: LuaTableDeleteMethod<TKey>;
592+
isEmpty: LuaTableIsEmptyMethod;
578593
}
579594

580595
/**
@@ -612,6 +627,7 @@ declare interface LuaMap<K extends AnyNotNil = AnyNotNil, V = any> extends LuaPa
612627
set: LuaTableSetMethod<K, V>;
613628
has: LuaTableHasMethod<K>;
614629
delete: LuaTableDeleteMethod<K>;
630+
isEmpty: LuaTableIsEmptyMethod;
615631
}
616632

617633
/**
@@ -633,6 +649,7 @@ declare const LuaMap: (new <K extends AnyNotNil = AnyNotNil, V = any>() => LuaMa
633649
declare interface ReadonlyLuaMap<K extends AnyNotNil = AnyNotNil, V = any> extends LuaPairsIterable<K, V> {
634650
get: LuaTableGetMethod<K, V | undefined>;
635651
has: LuaTableHasMethod<K>;
652+
isEmpty: LuaTableIsEmptyMethod;
636653
}
637654

638655
/**
@@ -645,6 +662,7 @@ declare interface LuaSet<T extends AnyNotNil = AnyNotNil> extends LuaPairsKeyIte
645662
add: LuaTableAddKeyMethod<T>;
646663
has: LuaTableHasMethod<T>;
647664
delete: LuaTableDeleteMethod<T>;
665+
isEmpty: LuaTableIsEmptyMethod;
648666
}
649667

650668
/**
@@ -662,6 +680,7 @@ declare const LuaSet: (new <T extends AnyNotNil = AnyNotNil>() => LuaSet<T>) & L
662680
*/
663681
declare interface ReadonlyLuaSet<T extends AnyNotNil = AnyNotNil> extends LuaPairsKeyIterable<T> {
664682
has: LuaTableHasMethod<T>;
683+
isEmpty: LuaTableIsEmptyMethod;
665684
}
666685

667686
interface ObjectConstructor {

src/transformation/utils/language-extensions.ts

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export enum ExtensionKind {
5353
TableSetMethodType = "TableSetMethod",
5454
TableAddKeyType = "TableAddKey",
5555
TableAddKeyMethodType = "TableAddKeyMethod",
56+
TableIsEmptyType = "TableIsEmpty",
57+
TableIsEmptyMethodType = "TableIsEmptyMethod",
5658
}
5759

5860
const extensionValues: Set<string> = new Set(Object.values(ExtensionKind));
@@ -122,31 +124,9 @@ export function getIterableExtensionKindForNode(
122124
return getIterableExtensionTypeForType(context, type);
123125
}
124126

125-
export const methodExtensionKinds: ReadonlySet<ExtensionKind> = new Set<ExtensionKind>([
126-
ExtensionKind.AdditionOperatorMethodType,
127-
ExtensionKind.SubtractionOperatorMethodType,
128-
ExtensionKind.MultiplicationOperatorMethodType,
129-
ExtensionKind.DivisionOperatorMethodType,
130-
ExtensionKind.ModuloOperatorMethodType,
131-
ExtensionKind.PowerOperatorMethodType,
132-
ExtensionKind.FloorDivisionOperatorMethodType,
133-
ExtensionKind.BitwiseAndOperatorMethodType,
134-
ExtensionKind.BitwiseOrOperatorMethodType,
135-
ExtensionKind.BitwiseExclusiveOrOperatorMethodType,
136-
ExtensionKind.BitwiseLeftShiftOperatorMethodType,
137-
ExtensionKind.BitwiseRightShiftOperatorMethodType,
138-
ExtensionKind.ConcatOperatorMethodType,
139-
ExtensionKind.LessThanOperatorMethodType,
140-
ExtensionKind.GreaterThanOperatorMethodType,
141-
ExtensionKind.NegationOperatorMethodType,
142-
ExtensionKind.BitwiseNotOperatorMethodType,
143-
ExtensionKind.LengthOperatorMethodType,
144-
ExtensionKind.TableDeleteMethodType,
145-
ExtensionKind.TableGetMethodType,
146-
ExtensionKind.TableHasMethodType,
147-
ExtensionKind.TableSetMethodType,
148-
ExtensionKind.TableAddKeyMethodType,
149-
]);
127+
export const methodExtensionKinds: ReadonlySet<ExtensionKind> = new Set<ExtensionKind>(
128+
Object.values(ExtensionKind).filter(key => key.endsWith("Method"))
129+
);
150130

151131
export function getNaryCallExtensionArgs(
152132
context: TransformationContext,

src/transformation/visitors/language-extensions/table.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getBinaryCallExtensionArgs,
77
getExtensionKindForNode,
88
getNaryCallExtensionArgs,
9+
getUnaryCallExtensionArg,
910
} from "../../utils/language-extensions";
1011
import { transformOrderedExpressions } from "../expression-list";
1112
import { LanguageExtensionCallTransformerMap } from "./call-extension";
@@ -27,6 +28,8 @@ export const tableExtensionTransformers: LanguageExtensionCallTransformerMap = {
2728
[ExtensionKind.TableSetMethodType]: transformTableSetExpression,
2829
[ExtensionKind.TableAddKeyType]: transformTableAddKeyExpression,
2930
[ExtensionKind.TableAddKeyMethodType]: transformTableAddKeyExpression,
31+
[ExtensionKind.TableIsEmptyType]: transformTableIsEmptyExpression,
32+
[ExtensionKind.TableIsEmptyMethodType]: transformTableIsEmptyExpression,
3033
};
3134

3235
function transformTableDeleteExpression(
@@ -120,3 +123,23 @@ function transformTableAddKeyExpression(
120123
);
121124
return lua.createNilLiteral();
122125
}
126+
127+
function transformTableIsEmptyExpression(
128+
context: TransformationContext,
129+
node: ts.CallExpression,
130+
extensionKind: ExtensionKind
131+
): lua.Expression {
132+
const args = getUnaryCallExtensionArg(context, node, extensionKind);
133+
if (!args) {
134+
return lua.createNilLiteral();
135+
}
136+
137+
const table = context.transformExpression(args);
138+
// next(arg0) == nil
139+
return lua.createBinaryExpression(
140+
lua.createCallExpression(lua.createIdentifier("next"), [table], node),
141+
lua.createNilLiteral(),
142+
lua.SyntaxKind.EqualityOperator,
143+
node
144+
);
145+
}

test/unit/language-extensions/table.spec.ts

Lines changed: 81 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ describe("LuaTableDelete extension", () => {
221221
.expectToEqual({ table: { baz: "baz" } });
222222
});
223223

224-
test("LuaTableHasMethod method", () => {
224+
test("LuaTableDeleteMethod method", () => {
225225
util.testModule`
226226
interface TableWithDelete {
227227
delete: LuaTableDeleteMethod<string>;
@@ -235,33 +235,6 @@ describe("LuaTableDelete extension", () => {
235235
.withLanguageExtensions()
236236
.expectToEqual({ table: { bar: 12 } });
237237
});
238-
239-
test.each([
240-
["LuaTableDelete<{}, string>", 'func({}, "foo")', true],
241-
["LuaTableDelete<{}, string>", '"truthy" && func({}, "foo")', true],
242-
["LuaTableSet<{}, string, number>", 'func({}, "foo", 3)', undefined],
243-
])("Table functions used as expression", (funcType, expression, value) => {
244-
util.testModule`
245-
declare const func: ${funcType}
246-
export const result = ${expression}
247-
`
248-
.withLanguageExtensions()
249-
.setReturnExport("result")
250-
.expectToEqual(value);
251-
});
252-
253-
test.each([
254-
["LuaTableDeleteMethod<string>", 'tbl.func("foo")', true],
255-
["LuaTableSetMethod<string, number>", 'tbl.func("foo", 3)', undefined],
256-
])("Table methods used as expression", (funcType, expression, value) => {
257-
util.testModule`
258-
const tbl = {} as { func: ${funcType} }
259-
export const result = ${expression}
260-
`
261-
.withLanguageExtensions()
262-
.setReturnExport("result")
263-
.expectToEqual(value);
264-
});
265238
});
266239

267240
describe("LuaTableAddKey extension", () => {
@@ -298,11 +271,67 @@ describe("LuaTableAddKey extension", () => {
298271
.withLanguageExtensions()
299272
.expectToEqual({ table: { bar: true } });
300273
});
274+
});
275+
276+
describe("LuaIsEmpty extension", () => {
277+
test("LuaIsEmpty standalone function", () => {
278+
util.testModule`
279+
declare const isTableEmpty: LuaTableIsEmpty<{}>;
280+
281+
const table = { foo: "bar", baz: "baz" };
282+
const emptyTable = {};
283+
284+
export const result = [isTableEmpty(table), isTableEmpty(emptyTable)];
285+
`
286+
.withLanguageExtensions()
287+
.expectToEqual({ result: [false, true] });
288+
});
289+
290+
test("LuaIsEmpty namespace function", () => {
291+
util.testModule`
292+
declare namespace Table {
293+
export const isTableEmpty: LuaTableIsEmpty<{}>;
294+
}
295+
296+
const table = { foo: "bar", baz: "baz" };
297+
const emptyTable = {};
298+
299+
export const result = [Table.isTableEmpty(table), Table.isTableEmpty(emptyTable)];
300+
`
301+
.withLanguageExtensions()
302+
.expectToEqual({ result: [false, true] });
303+
});
301304

305+
test("LuaTableIsEmptyMethod method", () => {
306+
util.testModule`
307+
interface TableWithIsEmpty {
308+
isEmpty: LuaTableIsEmptyMethod;
309+
set: LuaTableSetMethod<string, number>;
310+
}
311+
const table = {} as TableWithIsEmpty;
312+
table.set("foo", 42);
313+
table.set("bar", 12);
314+
315+
const emptyTable = {} as TableWithIsEmpty;
316+
317+
export const result = [table.isEmpty(), emptyTable.isEmpty()];
318+
`
319+
.withLanguageExtensions()
320+
.expectToEqual({ result: [false, true] });
321+
});
322+
});
323+
324+
describe("Table extensions use as expression", () => {
302325
test.each([
303326
["LuaTableAddKey<{}, string>", 'func({}, "foo")', undefined],
304327
["LuaTableAddKey<{}, string>", '"truthy" && func({}, "foo")', undefined],
305-
])("Table functions used as expression", (funcType, expression, value) => {
328+
["LuaTableDelete<{}, string>", 'func({}, "foo")', true],
329+
["LuaTableDelete<{}, string>", '"truthy" && func({}, "foo")', true],
330+
["LuaTableSet<{}, string, number>", 'func({}, "foo", 3)', undefined],
331+
["LuaTableIsEmpty<{}>", "func({})", true],
332+
["LuaTableIsEmpty<{}>", 'func({ foo: "bar", baz: "baz" })', false],
333+
["LuaTableIsEmpty<{}>", '"truthy" && func({})', true],
334+
])("functions used as expression", (funcType, expression, value) => {
306335
util.testModule`
307336
declare const func: ${funcType}
308337
export const result = ${expression}
@@ -312,14 +341,19 @@ describe("LuaTableAddKey extension", () => {
312341
.expectToEqual(value);
313342
});
314343

315-
test("Method used as expression", () => {
344+
test.each([
345+
["LuaTableDeleteMethod<string>", 'tbl.func("foo")', true],
346+
["LuaTableSetMethod<string, number>", 'tbl.func("foo", 3)', undefined],
347+
["LuaTableAddKeyMethod<string>", 'tbl.func("foo")', undefined],
348+
["LuaTableIsEmpty<{}>", "tbl.func({})", true],
349+
])("methods used as expression", (funcType, expression, value) => {
316350
util.testModule`
317-
const tbl = {} as { func: LuaTableAddKeyMethod<string> }
318-
export const result = tbl.func("foo")
351+
const tbl = {} as { func: ${funcType} }
352+
export const result = ${expression}
319353
`
320354
.withLanguageExtensions()
321355
.setReturnExport("result")
322-
.expectToEqual(undefined);
356+
.expectToEqual(value);
323357
});
324358
});
325359

@@ -415,9 +449,22 @@ describe("LuaTable extension interface", () => {
415449
.expectToEqual({ baz: 5 });
416450
});
417451

452+
test("table isEmpty", () => {
453+
util.testFunction`
454+
const tbl = new LuaTable<string, number>();
455+
tbl.set("foo", 1);
456+
457+
const emptyTbl = new LuaTable<string, number>();
458+
459+
return [tbl.isEmpty(), emptyTbl.isEmpty()];
460+
`
461+
.withLanguageExtensions()
462+
.expectToEqual([false, true]);
463+
});
464+
418465
test("table add", () => {
419466
util.testFunction`
420-
const tbl = new LuaSet<string>()
467+
const tbl = new LuaSet<string>();
421468
tbl.add("foo");
422469
return tbl
423470
`

0 commit comments

Comments
 (0)