Skip to content

Commit 5224ca0

Browse files
committed
Add custom symbols
1 parent 6e0d84f commit 5224ca0

File tree

5 files changed

+144
-1
lines changed

5 files changed

+144
-1
lines changed

src/LuaLib.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export enum LuaLibFeature {
3333
StringSplit = "StringSplit",
3434
StringConcat = "StringConcat",
3535
Symbol = "Symbol",
36+
SymbolRegistry = "SymbolRegistry",
3637
}
3738

3839
const luaLibDependencies: {[lib in LuaLibFeature]?: LuaLibFeature[]} = {
@@ -41,6 +42,7 @@ const luaLibDependencies: {[lib in LuaLibFeature]?: LuaLibFeature[]} = {
4142
Set: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
4243
WeakMap: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
4344
WeakSet: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
45+
SymbolRegistry: [LuaLibFeature.Symbol],
4446
};
4547

4648
export class LuaLib {

src/LuaTransformer.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2815,6 +2815,11 @@ export class LuaTransformer {
28152815
parameters = this.transformArguments(node.arguments, signature);
28162816
}
28172817

2818+
const expressionType = this.checker.getTypeAtLocation(node.expression);
2819+
if (expressionType.symbol && expressionType.symbol.escapedName === "SymbolConstructor") {
2820+
return this.transformLuaLibFunction(LuaLibFeature.Symbol, node, ...parameters);
2821+
}
2822+
28182823
const callExpression = tstl.createCallExpression(callPath, parameters);
28192824
return wrapResult ? this.wrapInTable(callExpression) : callExpression;
28202825
}
@@ -2850,6 +2855,10 @@ export class LuaTransformer {
28502855
return this.transformObjectCallExpression(node);
28512856
}
28522857

2858+
if (ownerType.symbol && ownerType.symbol.escapedName === "SymbolConstructor") {
2859+
return this.transformSymbolCallExpression(node);
2860+
}
2861+
28532862
switch (ownerType.flags) {
28542863
case ts.TypeFlags.String:
28552864
case ts.TypeFlags.StringLiteral:
@@ -3300,6 +3309,28 @@ export class LuaTransformer {
33003309
}
33013310
}
33023311

3312+
// Transpile a Symbol._ property
3313+
public transformSymbolCallExpression(expression: ts.CallExpression): tstl.CallExpression {
3314+
const method = expression.expression as ts.PropertyAccessExpression;
3315+
const parameters = this.transformArguments(expression.arguments);
3316+
const methodName = method.name.escapedText;
3317+
3318+
switch (methodName) {
3319+
case "for":
3320+
case "keyFor":
3321+
this.importLuaLibFeature(LuaLibFeature.SymbolRegistry);
3322+
const upperMethodName = methodName[0].toUpperCase() + methodName.slice(1);
3323+
const functionIdentifier = tstl.createIdentifier(`__TS__SymbolRegistry${upperMethodName}`);
3324+
return tstl.createCallExpression(functionIdentifier, parameters, expression);
3325+
default:
3326+
throw TSTLErrors.UnsupportedForTarget(
3327+
`symbol property ${methodName}`,
3328+
this.options.luaTarget,
3329+
expression
3330+
);
3331+
}
3332+
}
3333+
33033334
public transformArrayCallExpression(node: ts.CallExpression): tstl.CallExpression {
33043335
const expression = node.expression as ts.PropertyAccessExpression;
33053336
const params = this.transformArguments(node.arguments);

src/lualib/Symbol.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
declare function setmetatable<T extends object>(obj: T, metatable: any): T;
2+
3+
const symbolMetatable = {
4+
__tostring(): string {
5+
if (this.description === undefined) {
6+
return 'Symbol()';
7+
} else {
8+
return 'Symbol(' + this.description + ')';
9+
}
10+
},
11+
};
12+
13+
function __TS__Symbol(description?: string | number): symbol {
14+
return setmetatable({ description }, symbolMetatable) as any;
15+
}
16+
117
Symbol = {
2-
iterator: {},
18+
iterator: __TS__Symbol('Symbol.iterator'),
319
} as any;

src/lualib/SymbolRegistry.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const symbolRegistry: Record<string, symbol> = {};
2+
3+
function __TS__SymbolRegistryFor(key: string): symbol {
4+
if (!symbolRegistry[key]) {
5+
symbolRegistry[key] = __TS__Symbol(key);
6+
}
7+
8+
return symbolRegistry[key];
9+
}
10+
11+
function __TS__SymbolRegistryKeyFor(sym: symbol): string {
12+
for (const key in symbolRegistry) {
13+
if (symbolRegistry[key] === sym) return key;
14+
}
15+
}

test/unit/lualib/symbol.spec.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { Expect, Test, TestCase } from "alsatian";
2+
import * as util from "../../src/util";
3+
import { CompilerOptions, LuaLibImportKind } from '../../../src/CompilerOptions';
4+
5+
export class SymbolTests {
6+
private compilerOptions: CompilerOptions = {
7+
lib: ["esnext"],
8+
luaLibImport: LuaLibImportKind.Require,
9+
};
10+
11+
@Test("symbol.toString()")
12+
@TestCase()
13+
@TestCase(1)
14+
@TestCase("name")
15+
public symbolToString(description?: string | number): void
16+
{
17+
const result = util.transpileAndExecute(`
18+
return Symbol(${JSON.stringify(description)}).toString();
19+
`, this.compilerOptions);
20+
21+
Expect(result).toBe(`Symbol(${description || ''})`);
22+
}
23+
24+
@Test("symbol.description")
25+
@TestCase()
26+
@TestCase(1)
27+
@TestCase("name")
28+
public symbolDescription(description?: string | number): void
29+
{
30+
const result = util.transpileAndExecute(`
31+
return Symbol(${JSON.stringify(description)}).description;
32+
`, this.compilerOptions);
33+
34+
Expect(result).toBe(description);
35+
}
36+
37+
@Test("Symbol.for")
38+
public symbolFor(): void
39+
{
40+
const result = util.transpileAndExecute(`
41+
return Symbol.for("name").description;
42+
`, this.compilerOptions);
43+
44+
Expect(result).toBe("name");
45+
}
46+
47+
@Test("Symbol.for reference")
48+
public symbolForReference(): void
49+
{
50+
const result = util.transpileAndExecute(`
51+
return Symbol.for("a") === Symbol.for("a");
52+
`);
53+
54+
Expect(result).toBe(true);
55+
}
56+
57+
@Test("Symbol.keyFor")
58+
public symbolKeyFor(): void
59+
{
60+
const result = util.transpileAndExecute(`
61+
const sym = Symbol.for("a");
62+
Symbol.for("b");
63+
return Symbol.keyFor(sym);
64+
`);
65+
66+
Expect(result).toBe("a");
67+
}
68+
69+
@Test("Symbol.keyFor empty")
70+
public symbolKeyForEmpty(): void
71+
{
72+
const result = util.transpileAndExecute(`
73+
Symbol.for("a");
74+
return Symbol.keyFor(Symbol());
75+
`);
76+
77+
Expect(result).toBe(undefined);
78+
}
79+
}

0 commit comments

Comments
 (0)