Skip to content

Commit 6ad63ec

Browse files
ark120202Perryvw
authored andcommitted
Custom symbols (#407)
* Add custom symbols * Add symbol uniqueness test * Rename private variables in symbol libs
1 parent 6e0d84f commit 6ad63ec

File tree

5 files changed

+156
-1
lines changed

5 files changed

+156
-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: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
declare function setmetatable<T extends object>(obj: T, metatable: any): T;
2+
3+
// tslint:disable-next-line: variable-name
4+
const ____symbolMetatable = {
5+
__tostring(): string {
6+
if (this.description === undefined) {
7+
return 'Symbol()';
8+
} else {
9+
return 'Symbol(' + this.description + ')';
10+
}
11+
},
12+
};
13+
14+
function __TS__Symbol(description?: string | number): symbol {
15+
return setmetatable({ description }, ____symbolMetatable) as any;
16+
}
17+
118
Symbol = {
2-
iterator: {},
19+
iterator: __TS__Symbol('Symbol.iterator'),
320
} as any;

src/lualib/SymbolRegistry.ts

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

test/unit/lualib/symbol.spec.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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 uniqueness")
38+
public symbolUniqueness(): void
39+
{
40+
const result = util.transpileAndExecute(`
41+
return Symbol("a") === Symbol("a");
42+
`);
43+
44+
Expect(result).toBe(false);
45+
}
46+
47+
@Test("Symbol.for")
48+
public symbolFor(): void
49+
{
50+
const result = util.transpileAndExecute(`
51+
return Symbol.for("name").description;
52+
`, this.compilerOptions);
53+
54+
Expect(result).toBe("name");
55+
}
56+
57+
@Test("Symbol.for non-uniqueness")
58+
public symbolForNonUniqueness(): void
59+
{
60+
const result = util.transpileAndExecute(`
61+
return Symbol.for("a") === Symbol.for("a");
62+
`);
63+
64+
Expect(result).toBe(true);
65+
}
66+
67+
@Test("Symbol.keyFor")
68+
public symbolKeyFor(): void
69+
{
70+
const result = util.transpileAndExecute(`
71+
const sym = Symbol.for("a");
72+
Symbol.for("b");
73+
return Symbol.keyFor(sym);
74+
`);
75+
76+
Expect(result).toBe("a");
77+
}
78+
79+
@Test("Symbol.keyFor empty")
80+
public symbolKeyForEmpty(): void
81+
{
82+
const result = util.transpileAndExecute(`
83+
Symbol.for("a");
84+
return Symbol.keyFor(Symbol());
85+
`);
86+
87+
Expect(result).toBe(undefined);
88+
}
89+
}

0 commit comments

Comments
 (0)