Skip to content

Commit cdd2f19

Browse files
committed
Add custom symbols
1 parent 573b762 commit cdd2f19

File tree

6 files changed

+152
-3
lines changed

6 files changed

+152
-3
lines changed

src/Compiler.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,14 @@ export function createStringCompilerProgram(
106106
}
107107
if (filename.indexOf(".d.ts") !== -1) {
108108
if (!libCache[filename]) {
109-
libCache[filename] =
110-
fs.readFileSync(path.join(path.dirname(require.resolve("typescript")), filename)).toString();
109+
const typeScriptDir = path.dirname(require.resolve("typescript"));
110+
const filePath = path.join(typeScriptDir, filename);
111+
if (fs.existsSync(filePath)) {
112+
libCache[filename] = fs.readFileSync(filePath).toString();
113+
} else {
114+
const pathWithLibPrefix = path.join(typeScriptDir, "lib." + filename);
115+
libCache[filename] = fs.readFileSync(pathWithLibPrefix).toString();
116+
}
111117
}
112118
return ts.createSourceFile(filename, libCache[filename], ts.ScriptTarget.Latest, false);
113119
}

src/LuaLib.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ export enum LuaLibFeature {
2727
StringSplit = "StringSplit",
2828
StringConcat = "StringConcat",
2929
Symbol = "Symbol",
30+
SymbolRegistry = "SymbolRegistry",
3031
}
3132

3233
const luaLibDependencies: {[lib in LuaLibFeature]?: LuaLibFeature[]} = {
3334
Iterator: [LuaLibFeature.Symbol],
3435
Map: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
3536
Set: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
37+
SymbolRegistry: [LuaLibFeature.Symbol],
3638
};
3739

3840
export class LuaLib {

src/LuaTransformer.ts

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

2814+
const expressionType = this.checker.getTypeAtLocation(node.expression);
2815+
if (expressionType.symbol && expressionType.symbol.escapedName === "SymbolConstructor") {
2816+
return this.transformLuaLibFunction(LuaLibFeature.Symbol, ...parameters);
2817+
}
2818+
28142819
const callExpression = tstl.createCallExpression(callPath, parameters);
28152820
return wrapResult ? this.wrapInTable(callExpression) : callExpression;
28162821
}
@@ -2844,6 +2849,10 @@ export class LuaTransformer {
28442849
);
28452850
}
28462851

2852+
if (ownerType.symbol && ownerType.symbol.escapedName === "SymbolConstructor") {
2853+
return this.transformSymbolCallExpression(node);
2854+
}
2855+
28472856
switch (ownerType.flags) {
28482857
case ts.TypeFlags.String:
28492858
case ts.TypeFlags.StringLiteral:
@@ -3269,6 +3278,28 @@ export class LuaTransformer {
32693278
}
32703279
}
32713280

3281+
// Transpile a Symbol._ property
3282+
public transformSymbolCallExpression(expression: ts.CallExpression): tstl.CallExpression {
3283+
const method = expression.expression as ts.PropertyAccessExpression;
3284+
const parameters = this.transformArguments(expression.arguments);
3285+
const methodName = method.name.escapedText;
3286+
3287+
switch (methodName) {
3288+
case "for":
3289+
case "keyFor":
3290+
this.importLuaLibFeature(LuaLibFeature.SymbolRegistry);
3291+
const upperMethodName = methodName[0].toUpperCase() + methodName.slice(1);
3292+
const functionIdentifier = tstl.createIdentifier(`__TS__SymbolRegistry${upperMethodName}`);
3293+
return tstl.createCallExpression(functionIdentifier, parameters);
3294+
default:
3295+
throw TSTLErrors.UnsupportedForTarget(
3296+
`symbol property ${methodName}`,
3297+
this.options.luaTarget,
3298+
expression
3299+
);
3300+
}
3301+
}
3302+
32723303
public transformArrayCallExpression(node: ts.CallExpression): tstl.CallExpression {
32733304
const expression = node.expression as ts.PropertyAccessExpression;
32743305
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)