Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions language-extensions/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,20 @@ declare type LuaTableDelete<TTable extends AnyTable, TKey extends AnyNotNil> = (
declare type LuaTableDeleteMethod<TKey extends AnyNotNil> = ((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<TTable extends AnyTable> = ((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
Expand All @@ -575,6 +589,7 @@ declare interface LuaTable<TKey extends AnyNotNil = AnyNotNil, TValue = any> ext
set: LuaTableSetMethod<TKey, TValue>;
has: LuaTableHasMethod<TKey>;
delete: LuaTableDeleteMethod<TKey>;
isEmpty: LuaTableIsEmptyMethod;
}

/**
Expand Down Expand Up @@ -612,6 +627,7 @@ declare interface LuaMap<K extends AnyNotNil = AnyNotNil, V = any> extends LuaPa
set: LuaTableSetMethod<K, V>;
has: LuaTableHasMethod<K>;
delete: LuaTableDeleteMethod<K>;
isEmpty: LuaTableIsEmptyMethod;
}

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

/**
Expand All @@ -645,6 +662,7 @@ declare interface LuaSet<T extends AnyNotNil = AnyNotNil> extends LuaPairsKeyIte
add: LuaTableAddKeyMethod<T>;
has: LuaTableHasMethod<T>;
delete: LuaTableDeleteMethod<T>;
isEmpty: LuaTableIsEmptyMethod;
}

/**
Expand All @@ -662,6 +680,7 @@ declare const LuaSet: (new <T extends AnyNotNil = AnyNotNil>() => LuaSet<T>) & L
*/
declare interface ReadonlyLuaSet<T extends AnyNotNil = AnyNotNil> extends LuaPairsKeyIterable<T> {
has: LuaTableHasMethod<T>;
isEmpty: LuaTableIsEmptyMethod;
}

interface ObjectConstructor {
Expand Down
30 changes: 5 additions & 25 deletions src/transformation/utils/language-extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export enum ExtensionKind {
TableSetMethodType = "TableSetMethod",
TableAddKeyType = "TableAddKey",
TableAddKeyMethodType = "TableAddKeyMethod",
TableIsEmptyType = "TableIsEmpty",
TableIsEmptyMethodType = "TableIsEmptyMethod",
}

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

export const methodExtensionKinds: ReadonlySet<ExtensionKind> = new Set<ExtensionKind>([
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<ExtensionKind> = new Set<ExtensionKind>(
Object.values(ExtensionKind).filter(key => key.endsWith("Method"))
);

export function getNaryCallExtensionArgs(
context: TransformationContext,
Expand Down
23 changes: 23 additions & 0 deletions src/transformation/visitors/language-extensions/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
getBinaryCallExtensionArgs,
getExtensionKindForNode,
getNaryCallExtensionArgs,
getUnaryCallExtensionArg,
} from "../../utils/language-extensions";
import { transformOrderedExpressions } from "../expression-list";
import { LanguageExtensionCallTransformerMap } from "./call-extension";
Expand All @@ -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(
Expand Down Expand Up @@ -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
);
}
115 changes: 81 additions & 34 deletions test/unit/language-extensions/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ describe("LuaTableDelete extension", () => {
.expectToEqual({ table: { baz: "baz" } });
});

test("LuaTableHasMethod method", () => {
test("LuaTableDeleteMethod method", () => {
util.testModule`
interface TableWithDelete {
delete: LuaTableDeleteMethod<string>;
Expand All @@ -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<string>", 'tbl.func("foo")', true],
["LuaTableSetMethod<string, number>", '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", () => {
Expand Down Expand Up @@ -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<string, number>;
}
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}
Expand All @@ -312,14 +341,19 @@ describe("LuaTableAddKey extension", () => {
.expectToEqual(value);
});

test("Method used as expression", () => {
test.each([
["LuaTableDeleteMethod<string>", 'tbl.func("foo")', true],
["LuaTableSetMethod<string, number>", 'tbl.func("foo", 3)', undefined],
["LuaTableAddKeyMethod<string>", 'tbl.func("foo")', undefined],
["LuaTableIsEmpty<{}>", "tbl.func({})", true],
])("methods used as expression", (funcType, expression, value) => {
util.testModule`
const tbl = {} as { func: LuaTableAddKeyMethod<string> }
export const result = tbl.func("foo")
const tbl = {} as { func: ${funcType} }
export const result = ${expression}
`
.withLanguageExtensions()
.setReturnExport("result")
.expectToEqual(undefined);
.expectToEqual(value);
});
});

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

test("table isEmpty", () => {
util.testFunction`
const tbl = new LuaTable<string, number>();
tbl.set("foo", 1);

const emptyTbl = new LuaTable<string, number>();

return [tbl.isEmpty(), emptyTbl.isEmpty()];
`
.withLanguageExtensions()
.expectToEqual([false, true]);
});

test("table add", () => {
util.testFunction`
const tbl = new LuaSet<string>()
const tbl = new LuaSet<string>();
tbl.add("foo");
return tbl
`
Expand Down