Skip to content

Commit bfb89e9

Browse files
authored
Implemented LuaTableHas and LuaTableDelete (#1030)
* Add tests for luatable has * Implement LuaTable has * LuaTableDelete declaration and tests * Implemented LuaTableDelete
1 parent ffde390 commit bfb89e9

File tree

8 files changed

+395
-10
lines changed

8 files changed

+395
-10
lines changed

language-extensions/index.d.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,44 @@ declare type LuaTableSet<TTable extends AnyTable, TKey extends AnyNotNil, TValue
490490
declare type LuaTableSetMethod<TKey extends AnyNotNil, TValue> = ((key: TKey, value: TValue) => void) &
491491
LuaExtension<"__luaTableSetMethodBrand">;
492492

493+
/**
494+
* Calls to functions with this type are translated to `table[key] ~= nil`.
495+
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
496+
*
497+
* @param TTable The type to access as a Lua table.
498+
* @param TKey The type of the key to use to access the table.
499+
*/
500+
declare type LuaTableHas<TTable extends AnyTable, TKey extends AnyNotNil> = ((table: TTable, key: TKey) => boolean) &
501+
LuaExtension<"__luaTableHasBrand">;
502+
503+
/**
504+
* Calls to methods with this type are translated to `table[key] ~= nil`, where `table` is the object with the method.
505+
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
506+
*
507+
* @param TKey The type of the key to use to access the table.
508+
*/
509+
declare type LuaTableHasMethod<TKey extends AnyNotNil> = ((key: TKey) => boolean) &
510+
LuaExtension<"__luaTableHasMethodBrand">;
511+
512+
/**
513+
* Calls to functions with this type are translated to `table[key] = nil`.
514+
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
515+
*
516+
* @param TTable The type to access as a Lua table.
517+
* @param TKey The type of the key to use to access the table.
518+
*/
519+
declare type LuaTableDelete<TTable extends AnyTable, TKey extends AnyNotNil> = ((table: TTable, key: TKey) => boolean) &
520+
LuaExtension<"__luaTableDeleteBrand">;
521+
522+
/**
523+
* Calls to methods with this type are translated to `table[key] = nil`, where `table` is the object with the method.
524+
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
525+
*
526+
* @param TKey The type of the key to use to access the table.
527+
*/
528+
declare type LuaTableDeleteMethod<TKey extends AnyNotNil> = ((key: TKey) => boolean) &
529+
LuaExtension<"__luaTableDeleteMethodBrand">;
530+
493531
/**
494532
* A convenience type for working directly with a Lua table.
495533
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
@@ -501,6 +539,8 @@ declare interface LuaTable<TKey extends AnyTable = AnyNotNil, TValue = any> {
501539
length: LuaLengthMethod<number>;
502540
get: LuaTableGetMethod<TKey, TValue>;
503541
set: LuaTableSetMethod<TKey, TValue>;
542+
has: LuaTableHasMethod<TKey>;
543+
delete: LuaTableDeleteMethod<TKey>;
504544
}
505545

506546
/**

src/transformation/utils/diagnostics.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ export const invalidTableExtensionUse = createErrorDiagnosticFactory(
158158
"This function must be called directly and cannot be referred to."
159159
);
160160

161+
export const invalidTableDeleteExpression = createErrorDiagnosticFactory(
162+
"Table delete extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement."
163+
);
164+
161165
export const invalidTableSetExpression = createErrorDiagnosticFactory(
162166
"Table set extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement."
163167
);

src/transformation/utils/language-extensions.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,12 @@ export enum ExtensionKind {
4444
LengthOperatorType = "LengthOperatorType",
4545
LengthOperatorMethodType = "LengthOperatorMethodType",
4646
TableNewType = "TableNewType",
47+
TableDeleteType = "TableDeleteType",
48+
TableDeleteMethodType = "TableDeleteMethodType",
4749
TableGetType = "TableGetType",
4850
TableGetMethodType = "TableGetMethodType",
51+
TableHasType = "TableHasType",
52+
TableHasMethodType = "TableHasMethodType",
4953
TableSetType = "TableSetType",
5054
TableSetMethodType = "TableSetMethodType",
5155
}
@@ -99,8 +103,12 @@ const extensionKindToTypeBrand: { [T in ExtensionKind]: string } = {
99103
[ExtensionKind.LengthOperatorType]: "__luaLengthBrand",
100104
[ExtensionKind.LengthOperatorMethodType]: "__luaLengthMethodBrand",
101105
[ExtensionKind.TableNewType]: "__luaTableNewBrand",
106+
[ExtensionKind.TableDeleteType]: "__luaTableDeleteBrand",
107+
[ExtensionKind.TableDeleteMethodType]: "__luaTableDeleteMethodBrand",
102108
[ExtensionKind.TableGetType]: "__luaTableGetBrand",
103109
[ExtensionKind.TableGetMethodType]: "__luaTableGetMethodBrand",
110+
[ExtensionKind.TableHasType]: "__luaTableHasBrand",
111+
[ExtensionKind.TableHasMethodType]: "__luaTableHasMethodBrand",
104112
[ExtensionKind.TableSetType]: "__luaTableSetBrand",
105113
[ExtensionKind.TableSetMethodType]: "__luaTableSetMethodBrand",
106114
};

src/transformation/visitors/call.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ import { transformLuaTableCallExpression } from "./lua-table";
1414
import { shouldMultiReturnCallBeWrapped } from "./language-extensions/multi";
1515
import { isOperatorMapping, transformOperatorMappingExpression } from "./language-extensions/operators";
1616
import {
17+
isTableDeleteCall,
1718
isTableGetCall,
19+
isTableHasCall,
1820
isTableSetCall,
21+
transformTableDeleteExpression,
1922
transformTableGetExpression,
23+
transformTableHasExpression,
2024
transformTableSetExpression,
2125
} from "./language-extensions/table";
22-
import { invalidTableSetExpression } from "../utils/diagnostics";
26+
import { invalidTableDeleteExpression, invalidTableSetExpression } from "../utils/diagnostics";
2327
import {
2428
ImmediatelyInvokedFunctionParameters,
2529
transformToImmediatelyInvokedFunctionExpression,
@@ -242,10 +246,23 @@ export const transformCallExpression: FunctionVisitor<ts.CallExpression> = (node
242246
return transformOperatorMappingExpression(context, node);
243247
}
244248

249+
if (isTableDeleteCall(context, node)) {
250+
context.diagnostics.push(invalidTableDeleteExpression(node));
251+
return transformToImmediatelyInvokedFunctionExpression(
252+
context,
253+
() => ({ statements: transformTableDeleteExpression(context, node), result: lua.createNilLiteral() }),
254+
node
255+
);
256+
}
257+
245258
if (isTableGetCall(context, node)) {
246259
return transformTableGetExpression(context, node);
247260
}
248261

262+
if (isTableHasCall(context, node)) {
263+
return transformTableHasExpression(context, node);
264+
}
265+
249266
if (isTableSetCall(context, node)) {
250267
context.diagnostics.push(invalidTableSetExpression(node));
251268
return transformToImmediatelyInvokedFunctionExpression(

src/transformation/visitors/expression-statement.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import * as ts from "typescript";
22
import * as lua from "../../LuaAST";
33
import { FunctionVisitor } from "../context";
44
import { transformBinaryExpressionStatement } from "./binary-expression";
5-
import { isTableSetCall, transformTableSetExpression } from "./language-extensions/table";
5+
import {
6+
isTableDeleteCall,
7+
isTableSetCall,
8+
transformTableDeleteExpression,
9+
transformTableSetExpression,
10+
} from "./language-extensions/table";
611
import { transformLuaTableExpressionStatement } from "./lua-table";
712
import { transformUnaryExpressionStatement } from "./unary-expression";
813

@@ -12,6 +17,10 @@ export const transformExpressionStatement: FunctionVisitor<ts.ExpressionStatemen
1217
return luaTableResult;
1318
}
1419

20+
if (ts.isCallExpression(node.expression) && isTableDeleteCall(context, node.expression)) {
21+
return transformTableDeleteExpression(context, node.expression);
22+
}
23+
1524
if (ts.isCallExpression(node.expression) && isTableSetCall(context, node.expression)) {
1625
return transformTableSetExpression(context, node.expression);
1726
}

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

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,24 @@ import * as extensions from "../../utils/language-extensions";
55
import { getFunctionTypeForCall } from "../../utils/typescript";
66
import { assert } from "../../../utils";
77

8+
const tableDeleteExtensions = [
9+
extensions.ExtensionKind.TableDeleteType,
10+
extensions.ExtensionKind.TableDeleteMethodType,
11+
];
12+
813
const tableGetExtensions = [extensions.ExtensionKind.TableGetType, extensions.ExtensionKind.TableGetMethodType];
914

15+
const tableHasExtensions = [extensions.ExtensionKind.TableHasType, extensions.ExtensionKind.TableHasMethodType];
16+
1017
const tableSetExtensions = [extensions.ExtensionKind.TableSetType, extensions.ExtensionKind.TableSetMethodType];
1118

12-
const tableExtensions = [extensions.ExtensionKind.TableNewType, ...tableGetExtensions, ...tableSetExtensions];
19+
const tableExtensions = [
20+
extensions.ExtensionKind.TableNewType,
21+
...tableDeleteExtensions,
22+
...tableGetExtensions,
23+
...tableHasExtensions,
24+
...tableSetExtensions,
25+
];
1326

1427
function getTableExtensionKindForCall(
1528
context: TransformationContext,
@@ -25,10 +38,18 @@ export function isTableExtensionIdentifier(context: TransformationContext, node:
2538
return tableExtensions.some(extensionKind => extensions.isExtensionType(type, extensionKind));
2639
}
2740

41+
export function isTableDeleteCall(context: TransformationContext, node: ts.CallExpression) {
42+
return getTableExtensionKindForCall(context, node, tableDeleteExtensions) !== undefined;
43+
}
44+
2845
export function isTableGetCall(context: TransformationContext, node: ts.CallExpression) {
2946
return getTableExtensionKindForCall(context, node, tableGetExtensions) !== undefined;
3047
}
3148

49+
export function isTableHasCall(context: TransformationContext, node: ts.CallExpression) {
50+
return getTableExtensionKindForCall(context, node, tableHasExtensions) !== undefined;
51+
}
52+
3253
export function isTableSetCall(context: TransformationContext, node: ts.CallExpression) {
3354
return getTableExtensionKindForCall(context, node, tableSetExtensions) !== undefined;
3455
}
@@ -38,6 +59,27 @@ export function isTableNewCall(context: TransformationContext, node: ts.NewExpre
3859
return extensions.isExtensionType(type, extensions.ExtensionKind.TableNewType);
3960
}
4061

62+
export function transformTableDeleteExpression(context: TransformationContext, node: ts.CallExpression): lua.Statement {
63+
const extensionKind = getTableExtensionKindForCall(context, node, tableDeleteExtensions);
64+
assert(extensionKind);
65+
66+
const args = node.arguments.slice();
67+
if (
68+
args.length === 1 &&
69+
(ts.isPropertyAccessExpression(node.expression) || ts.isElementAccessExpression(node.expression))
70+
) {
71+
// In case of method (no table argument), push method owner to front of args list
72+
args.unshift(node.expression.expression);
73+
}
74+
75+
// arg0[arg1] = nil
76+
return lua.createAssignmentStatement(
77+
lua.createTableIndexExpression(context.transformExpression(args[0]), context.transformExpression(args[1])),
78+
lua.createNilLiteral(),
79+
node
80+
);
81+
}
82+
4183
export function transformTableGetExpression(context: TransformationContext, node: ts.CallExpression): lua.Expression {
4284
const extensionKind = getTableExtensionKindForCall(context, node, tableGetExtensions);
4385
assert(extensionKind);
@@ -47,16 +89,46 @@ export function transformTableGetExpression(context: TransformationContext, node
4789
args.length === 1 &&
4890
(ts.isPropertyAccessExpression(node.expression) || ts.isElementAccessExpression(node.expression))
4991
) {
92+
// In case of method (no table argument), push method owner to front of args list
5093
args.unshift(node.expression.expression);
5194
}
5295

96+
// arg0[arg1]
5397
return lua.createTableIndexExpression(
5498
context.transformExpression(args[0]),
5599
context.transformExpression(args[1]),
56100
node
57101
);
58102
}
59103

104+
export function transformTableHasExpression(context: TransformationContext, node: ts.CallExpression): lua.Expression {
105+
const extensionKind = getTableExtensionKindForCall(context, node, tableHasExtensions);
106+
assert(extensionKind);
107+
108+
const args = node.arguments.slice();
109+
if (
110+
args.length === 1 &&
111+
(ts.isPropertyAccessExpression(node.expression) || ts.isElementAccessExpression(node.expression))
112+
) {
113+
// In case of method (no table argument), push method owner to front of args list
114+
args.unshift(node.expression.expression);
115+
}
116+
117+
// arg0[arg1]
118+
const tableIndexExpression = lua.createTableIndexExpression(
119+
context.transformExpression(args[0]),
120+
context.transformExpression(args[1])
121+
);
122+
123+
// arg0[arg1] ~= nil
124+
return lua.createBinaryExpression(
125+
tableIndexExpression,
126+
lua.createNilLiteral(),
127+
lua.SyntaxKind.InequalityOperator,
128+
node
129+
);
130+
}
131+
60132
export function transformTableSetExpression(context: TransformationContext, node: ts.CallExpression): lua.Statement {
61133
const extensionKind = getTableExtensionKindForCall(context, node, tableSetExtensions);
62134
assert(extensionKind);
@@ -66,15 +138,14 @@ export function transformTableSetExpression(context: TransformationContext, node
66138
args.length === 2 &&
67139
(ts.isPropertyAccessExpression(node.expression) || ts.isElementAccessExpression(node.expression))
68140
) {
141+
// In case of method (no table argument), push method owner to front of args list
69142
args.unshift(node.expression.expression);
70143
}
71144

145+
// arg0[arg1] = arg2
72146
return lua.createAssignmentStatement(
73-
lua.createTableIndexExpression(
74-
context.transformExpression(args[0]),
75-
context.transformExpression(args[1]),
76-
node
77-
),
78-
context.transformExpression(args[2])
147+
lua.createTableIndexExpression(context.transformExpression(args[0]), context.transformExpression(args[1])),
148+
context.transformExpression(args[2]),
149+
node
79150
);
80151
}

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,48 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`LuaTableDelete extension LuaTableDelete invalid use as expression ("const foo = [tableDelete({}, \\"foo\\")];"): code 1`] = `
4+
"foo = {
5+
(function()
6+
({}).foo = nil
7+
return nil
8+
end)()
9+
}"
10+
`;
11+
12+
exports[`LuaTableDelete extension LuaTableDelete invalid use as expression ("const foo = [tableDelete({}, \\"foo\\")];"): diagnostics 1`] = `"main.ts(3,26): error TSTL: Table delete extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement."`;
13+
14+
exports[`LuaTableDelete extension LuaTableDelete invalid use as expression ("const foo = \`\${tableDelete({}, \\"foo\\")}\`;"): code 1`] = `
15+
"foo = tostring(
16+
(function()
17+
({}).foo = nil
18+
return nil
19+
end)()
20+
)"
21+
`;
22+
23+
exports[`LuaTableDelete extension LuaTableDelete invalid use as expression ("const foo = \`\${tableDelete({}, \\"foo\\")}\`;"): diagnostics 1`] = `"main.ts(3,28): error TSTL: Table delete extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement."`;
24+
25+
exports[`LuaTableDelete extension LuaTableDelete invalid use as expression ("const foo = tableDelete({}, \\"foo\\");"): code 1`] = `
26+
"foo = (function()
27+
({}).foo = nil
28+
return nil
29+
end)()"
30+
`;
31+
32+
exports[`LuaTableDelete extension LuaTableDelete invalid use as expression ("const foo = tableDelete({}, \\"foo\\");"): diagnostics 1`] = `"main.ts(3,25): error TSTL: Table delete extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement."`;
33+
34+
exports[`LuaTableDelete extension LuaTableDelete invalid use as expression ("declare function foo(arg: any): void; foo(tableDelete({}, \\"foo\\"));"): code 1`] = `
35+
"foo(
36+
_G,
37+
(function()
38+
({}).foo = nil
39+
return nil
40+
end)()
41+
)"
42+
`;
43+
44+
exports[`LuaTableDelete extension LuaTableDelete invalid use as expression ("declare function foo(arg: any): void; foo(tableDelete({}, \\"foo\\"));"): diagnostics 1`] = `"main.ts(3,55): error TSTL: Table delete extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement."`;
45+
346
exports[`LuaTableGet & LuaTableSet extensions LuaTableSet invalid use as expression ("const foo = [setTable({}, \\"foo\\", 3)];"): code 1`] = `
447
"foo = {
548
(function()
@@ -62,3 +105,23 @@ exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo: unknown =
62105
exports[`LuaTableGet & LuaTableSet extensions invalid use ("declare function foo(getTable: LuaTableGet<{}, string, number>): void; foo(getTable);"): code 1`] = `"foo(_G, getTable)"`;
63106
64107
exports[`LuaTableGet & LuaTableSet extensions invalid use ("declare function foo(getTable: LuaTableGet<{}, string, number>): void; foo(getTable);"): diagnostics 1`] = `"main.ts(3,88): error TSTL: This function must be called directly and cannot be referred to."`;
108+
109+
exports[`LuaTableHas extension invalid use ("const foo = (tableHas as any)(1, 2);"): code 1`] = `"foo = tableHas(_G, 1, 2)"`;
110+
111+
exports[`LuaTableHas extension invalid use ("const foo = (tableHas as any)(1, 2);"): diagnostics 1`] = `"main.ts(3,26): error TSTL: This function must be called directly and cannot be referred to."`;
112+
113+
exports[`LuaTableHas extension invalid use ("const foo = [tableHas];"): code 1`] = `"foo = {tableHas}"`;
114+
115+
exports[`LuaTableHas extension invalid use ("const foo = [tableHas];"): diagnostics 1`] = `"main.ts(3,26): error TSTL: This function must be called directly and cannot be referred to."`;
116+
117+
exports[`LuaTableHas extension invalid use ("const foo = \`\${tableHas}\`;"): code 1`] = `"foo = tostring(tableHas)"`;
118+
119+
exports[`LuaTableHas extension invalid use ("const foo = \`\${tableHas}\`;"): diagnostics 1`] = `"main.ts(3,28): error TSTL: This function must be called directly and cannot be referred to."`;
120+
121+
exports[`LuaTableHas extension invalid use ("const foo: unknown = tableHas;"): code 1`] = `"foo = tableHas"`;
122+
123+
exports[`LuaTableHas extension invalid use ("const foo: unknown = tableHas;"): diagnostics 1`] = `"main.ts(3,34): error TSTL: This function must be called directly and cannot be referred to."`;
124+
125+
exports[`LuaTableHas extension invalid use ("declare function foo(tableHas: LuaTableHas<{}, string>): void; foo(tableHas);"): code 1`] = `"foo(_G, tableHas)"`;
126+
127+
exports[`LuaTableHas extension invalid use ("declare function foo(tableHas: LuaTableHas<{}, string>): void; foo(tableHas);"): diagnostics 1`] = `"main.ts(3,80): error TSTL: This function must be called directly and cannot be referred to."`;

0 commit comments

Comments
 (0)