From 5224ca06ad91dab55deb391bc16d22f004a8103e Mon Sep 17 00:00:00 2001 From: ark120202 Date: Fri, 15 Feb 2019 19:54:27 +0500 Subject: [PATCH 1/3] Add custom symbols --- src/LuaLib.ts | 2 + src/LuaTransformer.ts | 31 +++++++++++++ src/lualib/Symbol.ts | 18 +++++++- src/lualib/SymbolRegistry.ts | 15 +++++++ test/unit/lualib/symbol.spec.ts | 79 +++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 src/lualib/SymbolRegistry.ts create mode 100644 test/unit/lualib/symbol.spec.ts diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 8e007318f..83296aad3 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -33,6 +33,7 @@ export enum LuaLibFeature { StringSplit = "StringSplit", StringConcat = "StringConcat", Symbol = "Symbol", + SymbolRegistry = "SymbolRegistry", } const luaLibDependencies: {[lib in LuaLibFeature]?: LuaLibFeature[]} = { @@ -41,6 +42,7 @@ const luaLibDependencies: {[lib in LuaLibFeature]?: LuaLibFeature[]} = { Set: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol], WeakMap: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol], WeakSet: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol], + SymbolRegistry: [LuaLibFeature.Symbol], }; export class LuaLib { diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 45f886ef6..eee7a99c9 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2815,6 +2815,11 @@ export class LuaTransformer { parameters = this.transformArguments(node.arguments, signature); } + const expressionType = this.checker.getTypeAtLocation(node.expression); + if (expressionType.symbol && expressionType.symbol.escapedName === "SymbolConstructor") { + return this.transformLuaLibFunction(LuaLibFeature.Symbol, node, ...parameters); + } + const callExpression = tstl.createCallExpression(callPath, parameters); return wrapResult ? this.wrapInTable(callExpression) : callExpression; } @@ -2850,6 +2855,10 @@ export class LuaTransformer { return this.transformObjectCallExpression(node); } + if (ownerType.symbol && ownerType.symbol.escapedName === "SymbolConstructor") { + return this.transformSymbolCallExpression(node); + } + switch (ownerType.flags) { case ts.TypeFlags.String: case ts.TypeFlags.StringLiteral: @@ -3300,6 +3309,28 @@ export class LuaTransformer { } } + // Transpile a Symbol._ property + public transformSymbolCallExpression(expression: ts.CallExpression): tstl.CallExpression { + const method = expression.expression as ts.PropertyAccessExpression; + const parameters = this.transformArguments(expression.arguments); + const methodName = method.name.escapedText; + + switch (methodName) { + case "for": + case "keyFor": + this.importLuaLibFeature(LuaLibFeature.SymbolRegistry); + const upperMethodName = methodName[0].toUpperCase() + methodName.slice(1); + const functionIdentifier = tstl.createIdentifier(`__TS__SymbolRegistry${upperMethodName}`); + return tstl.createCallExpression(functionIdentifier, parameters, expression); + default: + throw TSTLErrors.UnsupportedForTarget( + `symbol property ${methodName}`, + this.options.luaTarget, + expression + ); + } + } + public transformArrayCallExpression(node: ts.CallExpression): tstl.CallExpression { const expression = node.expression as ts.PropertyAccessExpression; const params = this.transformArguments(node.arguments); diff --git a/src/lualib/Symbol.ts b/src/lualib/Symbol.ts index 478fd49c8..ebbe5a33f 100644 --- a/src/lualib/Symbol.ts +++ b/src/lualib/Symbol.ts @@ -1,3 +1,19 @@ +declare function setmetatable(obj: T, metatable: any): T; + +const symbolMetatable = { + __tostring(): string { + if (this.description === undefined) { + return 'Symbol()'; + } else { + return 'Symbol(' + this.description + ')'; + } + }, +}; + +function __TS__Symbol(description?: string | number): symbol { + return setmetatable({ description }, symbolMetatable) as any; +} + Symbol = { - iterator: {}, + iterator: __TS__Symbol('Symbol.iterator'), } as any; diff --git a/src/lualib/SymbolRegistry.ts b/src/lualib/SymbolRegistry.ts new file mode 100644 index 000000000..f07591cea --- /dev/null +++ b/src/lualib/SymbolRegistry.ts @@ -0,0 +1,15 @@ +const symbolRegistry: Record = {}; + +function __TS__SymbolRegistryFor(key: string): symbol { + if (!symbolRegistry[key]) { + symbolRegistry[key] = __TS__Symbol(key); + } + + return symbolRegistry[key]; +} + +function __TS__SymbolRegistryKeyFor(sym: symbol): string { + for (const key in symbolRegistry) { + if (symbolRegistry[key] === sym) return key; + } +} diff --git a/test/unit/lualib/symbol.spec.ts b/test/unit/lualib/symbol.spec.ts new file mode 100644 index 000000000..8f39adea4 --- /dev/null +++ b/test/unit/lualib/symbol.spec.ts @@ -0,0 +1,79 @@ +import { Expect, Test, TestCase } from "alsatian"; +import * as util from "../../src/util"; +import { CompilerOptions, LuaLibImportKind } from '../../../src/CompilerOptions'; + +export class SymbolTests { + private compilerOptions: CompilerOptions = { + lib: ["esnext"], + luaLibImport: LuaLibImportKind.Require, + }; + + @Test("symbol.toString()") + @TestCase() + @TestCase(1) + @TestCase("name") + public symbolToString(description?: string | number): void + { + const result = util.transpileAndExecute(` + return Symbol(${JSON.stringify(description)}).toString(); + `, this.compilerOptions); + + Expect(result).toBe(`Symbol(${description || ''})`); + } + + @Test("symbol.description") + @TestCase() + @TestCase(1) + @TestCase("name") + public symbolDescription(description?: string | number): void + { + const result = util.transpileAndExecute(` + return Symbol(${JSON.stringify(description)}).description; + `, this.compilerOptions); + + Expect(result).toBe(description); + } + + @Test("Symbol.for") + public symbolFor(): void + { + const result = util.transpileAndExecute(` + return Symbol.for("name").description; + `, this.compilerOptions); + + Expect(result).toBe("name"); + } + + @Test("Symbol.for reference") + public symbolForReference(): void + { + const result = util.transpileAndExecute(` + return Symbol.for("a") === Symbol.for("a"); + `); + + Expect(result).toBe(true); + } + + @Test("Symbol.keyFor") + public symbolKeyFor(): void + { + const result = util.transpileAndExecute(` + const sym = Symbol.for("a"); + Symbol.for("b"); + return Symbol.keyFor(sym); + `); + + Expect(result).toBe("a"); + } + + @Test("Symbol.keyFor empty") + public symbolKeyForEmpty(): void + { + const result = util.transpileAndExecute(` + Symbol.for("a"); + return Symbol.keyFor(Symbol()); + `); + + Expect(result).toBe(undefined); + } +} From 3a2156452cda2bb01fa4f9836f6d89ae35210ddf Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 16 Feb 2019 17:19:12 +0500 Subject: [PATCH 2/3] Add symbol uniqueness test --- test/unit/lualib/symbol.spec.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/unit/lualib/symbol.spec.ts b/test/unit/lualib/symbol.spec.ts index 8f39adea4..a15b79664 100644 --- a/test/unit/lualib/symbol.spec.ts +++ b/test/unit/lualib/symbol.spec.ts @@ -34,6 +34,16 @@ export class SymbolTests { Expect(result).toBe(description); } + @Test("symbol uniqueness") + public symbolUniqueness(): void + { + const result = util.transpileAndExecute(` + return Symbol("a") === Symbol("a"); + `); + + Expect(result).toBe(false); + } + @Test("Symbol.for") public symbolFor(): void { @@ -44,8 +54,8 @@ export class SymbolTests { Expect(result).toBe("name"); } - @Test("Symbol.for reference") - public symbolForReference(): void + @Test("Symbol.for non-uniqueness") + public symbolForNonUniqueness(): void { const result = util.transpileAndExecute(` return Symbol.for("a") === Symbol.for("a"); From 0eba3d392a1244d55e4af185fbebc1c4a1d2f042 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 16 Feb 2019 20:48:51 +0500 Subject: [PATCH 3/3] Rename private variables in symbol libs --- src/lualib/Symbol.ts | 5 +++-- src/lualib/SymbolRegistry.ts | 13 +++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lualib/Symbol.ts b/src/lualib/Symbol.ts index ebbe5a33f..2464b5f20 100644 --- a/src/lualib/Symbol.ts +++ b/src/lualib/Symbol.ts @@ -1,6 +1,7 @@ declare function setmetatable(obj: T, metatable: any): T; -const symbolMetatable = { +// tslint:disable-next-line: variable-name +const ____symbolMetatable = { __tostring(): string { if (this.description === undefined) { return 'Symbol()'; @@ -11,7 +12,7 @@ const symbolMetatable = { }; function __TS__Symbol(description?: string | number): symbol { - return setmetatable({ description }, symbolMetatable) as any; + return setmetatable({ description }, ____symbolMetatable) as any; } Symbol = { diff --git a/src/lualib/SymbolRegistry.ts b/src/lualib/SymbolRegistry.ts index f07591cea..c9c42e4e3 100644 --- a/src/lualib/SymbolRegistry.ts +++ b/src/lualib/SymbolRegistry.ts @@ -1,15 +1,16 @@ -const symbolRegistry: Record = {}; +// tslint:disable-next-line: variable-name +const ____symbolRegistry: Record = {}; function __TS__SymbolRegistryFor(key: string): symbol { - if (!symbolRegistry[key]) { - symbolRegistry[key] = __TS__Symbol(key); + if (!____symbolRegistry[key]) { + ____symbolRegistry[key] = __TS__Symbol(key); } - return symbolRegistry[key]; + return ____symbolRegistry[key]; } function __TS__SymbolRegistryKeyFor(sym: symbol): string { - for (const key in symbolRegistry) { - if (symbolRegistry[key] === sym) return key; + for (const key in ____symbolRegistry) { + if (____symbolRegistry[key] === sym) return key; } }