From 7a0e4201303282cca60098d52a4b63780261bfd8 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 10 Feb 2019 17:10:08 +0100 Subject: [PATCH 1/5] Enum identifier values --- src/LuaTransformer.ts | 58 +++++++++++++++++++--------- src/TSHelper.ts | 17 +++++++++ test/unit/enum.spec.ts | 87 +++++++++++++++++++++++++++++++++++------- 3 files changed, 131 insertions(+), 31 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index bd91692ef..62407661c 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -986,10 +986,12 @@ export class LuaTransformer { )); } } else { - const table: tstl.IdentifierOrTableIndexExpression = - this.transformIdentifierExpression(enumDeclaration.name); - const property = tstl.createTableIndexExpression(table, memberName, undefined); + const enumTable = this.transformIdentifierExpression(enumDeclaration.name); + const property = tstl.createTableIndexExpression(enumTable, memberName); result.push(tstl.createAssignmentStatement(property, enumMember.value, enumMember.original)); + + const valueIndex = tstl.createTableIndexExpression(enumTable, enumMember.value); + result.push(tstl.createAssignmentStatement(valueIndex, memberName, enumMember.original)); } } @@ -997,36 +999,58 @@ export class LuaTransformer { } public computeEnumMembers(node: ts.EnumDeclaration): - Array<{name: ts.PropertyName, value: tstl.NumericLiteral | tstl.StringLiteral, original: ts.Node}> { + Array<{name: ts.PropertyName, value: tstl.Expression, original: ts.Node}> { let numericValue = 0; let hasStringInitializers = false; + const valueMap = new Map(); + return node.members.map(member => { - let valueLiteral: tstl.NumericLiteral | tstl.StringLiteral; + let valueExpression: tstl.Expression; if (member.initializer) { - if (ts.isNumericLiteral(member.initializer)) { + if (ts.isNumericLiteral(member.initializer)) + { numericValue = Number(member.initializer.text); - valueLiteral = tstl.createNumericLiteral(numericValue); - } else if (ts.isStringLiteral(member.initializer)) { + valueExpression = tstl.createNumericLiteral(numericValue); + numericValue++; + } + else if (ts.isStringLiteral(member.initializer)) + { hasStringInitializers = true; - valueLiteral = tstl.createStringLiteral(member.initializer.text); - } else { - throw TSTLErrors.InvalidEnumMember(member.initializer); + valueExpression = tstl.createStringLiteral(member.initializer.text); } - } else if (hasStringInitializers) { + else + { + if (ts.isIdentifier(member.initializer)) { + const [isEnumMember, originalName] = tsHelper.isEnumMember(node, member.initializer); + if (isEnumMember) { + valueExpression = valueMap.get(originalName); + } else { + valueExpression = this.transformExpression(member.initializer); + } + } else { + valueExpression = this.transformExpression(member.initializer); + } + } + } + else if (hasStringInitializers) + { throw TSTLErrors.HeterogeneousEnum(node); - } else { - valueLiteral = tstl.createNumericLiteral(numericValue); + } + else + { + valueExpression = tstl.createNumericLiteral(numericValue); + numericValue++; } + valueMap.set(member.name, valueExpression); + const enumMember = { name: member.name, original: member, - value: valueLiteral, + value: valueExpression, }; - numericValue++; - return enumMember; }); } diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 389ccac37..a30670f8d 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -625,4 +625,21 @@ export class TSHelper { const firstDeclaration = this.getFirstDeclaration(symbol); return firstDeclaration === node; } + + public static isEnumMember(enumDeclaration: ts.EnumDeclaration, value: ts.Expression): [boolean, ts.PropertyName] { + if (ts.isIdentifier(value)) { + const enumMember = enumDeclaration.members.find(m => ts.isIdentifier(m.name) && m.name.text === value.text); + if (enumMember !== undefined) { + if (enumMember.initializer && ts.isIdentifier(enumMember.initializer)) { + return this.isEnumMember(enumDeclaration, enumMember.initializer); + } else { + return [true, enumMember.name]; + } + } else { + return [false, undefined]; + } + } else { + return [false, undefined]; + } + } } diff --git a/test/unit/enum.spec.ts b/test/unit/enum.spec.ts index b2a2a09ce..9fbf263f1 100644 --- a/test/unit/enum.spec.ts +++ b/test/unit/enum.spec.ts @@ -76,20 +76,6 @@ export class EnumTests { + "member values, or specify values (of the same type) for all members."); } - @Test("Unsuported enum") - public unsuportedEnum(): void { - // Transpile & Assert - Expect(() => { - const lua = util.transpileString( - `enum TestEnum { - val1 = [], - val2 = "ok", - val3 = "bye" - }` - ); - }).toThrowError(TranspileError, "Only numeric or string initializers allowed for enums."); - } - @Test("String literal name in enum") public stringLiteralNameEnum(): void { const code = `enum TestEnum { @@ -99,4 +85,77 @@ export class EnumTests { const result = util.transpileAndExecute(code); Expect(result).toBe("foo"); } + + @Test("Enum identifier value internal") + public enumIdentifierValueInternal(): void { + const result = util.transpileAndExecute( + `enum testEnum { + abc, + def, + ghi = def, + jkl, + } + return \`\${testEnum.abc},\${testEnum.def},\${testEnum.ghi},\${testEnum.jkl}\`;` + ); + + Expect(result).toBe("0,1,1,2"); + } + + @Test("Enum identifier value internal recursive") + public enumIdentifierValueInternalRecursive(): void { + const result = util.transpileAndExecute( + `enum testEnum { + abc, + def, + ghi = def, + jkl = ghi, + } + return \`\${testEnum.abc},\${testEnum.def},\${testEnum.ghi},\${testEnum.jkl}\`;` + ); + + Expect(result).toBe("0,1,1,1"); + } + + @Test("Enum identifier value external") + public enumIdentifierValueExternal(): void { + const result = util.transpileAndExecute( + `const ext = 6; + enum testEnum { + abc, + def, + ghi = ext, + } + return \`\${testEnum.abc},\${testEnum.def},\${testEnum.ghi}\`;` + ); + + Expect(result).toBe("0,1,6"); + } + + @Test("Enum reverse mapping") + public enumReverseMapping(): void { + const result = util.transpileAndExecute( + `enum testEnum { + abc, + def, + ghi + } + return testEnum[testEnum.abc] + testEnum[testEnum.ghi]` + ); + + Expect(result).toBe("abcghi"); + } + + @Test("Const enum index") + public constEnumIndex(): void { + const result = util.transpileAndExecute( + `const enum testEnum { + abc, + def, + ghi + } + return testEnum["def"];` + ); + + Expect(result).toBe(1); + } } From 2fbac252455d1553411f427a20b85f100c065a43 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 10 Feb 2019 21:39:41 +0100 Subject: [PATCH 2/5] Fixed indexing of const enums --- src/LuaTransformer.ts | 67 +++++++++++++++++++++++++++++------------- src/TSTLErrors.ts | 5 ++++ test/unit/enum.spec.ts | 8 ++--- 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 62407661c..9167582bc 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2749,26 +2749,7 @@ export class LuaTransformer { } if (type.symbol && (type.symbol.flags & ts.SymbolFlags.ConstEnum)) { - const propertyValueDeclaration = this.checker.getTypeAtLocation(node).symbol.valueDeclaration; - - if (propertyValueDeclaration && propertyValueDeclaration.kind === ts.SyntaxKind.EnumMember) { - const enumMember = propertyValueDeclaration as ts.EnumMember; - - if (enumMember.initializer) { - return this.transformExpression(enumMember.initializer); - } else { - const enumMembers = this.computeEnumMembers(enumMember.parent); - const memberPosition = enumMember.parent.members.indexOf(enumMember); - - if (memberPosition === -1) { - throw TSTLErrors.UnsupportedProperty(type.symbol.name, property, node); - } - - const value = tstl.cloneNode(enumMembers[memberPosition].value); - tstl.setNodeOriginal(value, enumMember); - return value; - } - } + return this.transformConstEnumValue(type, property, node); } this.checkForLuaLibType(type); @@ -2864,6 +2845,13 @@ export class LuaTransformer { const index = this.transformExpression(node.argumentExpression); const type = this.checker.getTypeAtLocation(node.expression); + + if (type.symbol && (type.symbol.flags & ts.SymbolFlags.ConstEnum) + && ts.isStringLiteral(node.argumentExpression)) + { + return this.transformConstEnumValue(type, node.argumentExpression.text, node); + } + if (tsHelper.isArrayType(type, this.checker)) { return tstl.createTableIndexExpression(table, this.expressionPlusOne(index), node); } else if (tsHelper.isStringType(type)) { @@ -2877,6 +2865,45 @@ export class LuaTransformer { } } + private transformConstEnumValue(enumType: ts.EnumType, memberName: string, tsOriginal: ts.Node): tstl.Expression { + // Assumption: the enum only has one declaration + const enumDeclaration = enumType.symbol.declarations.find(d => ts.isEnumDeclaration(d)) as ts.EnumDeclaration; + const enumMember = enumDeclaration.members + .find(m => ts.isIdentifier(m.name) && m.name.text === memberName); + + if (enumMember) { + if (enumMember.initializer) { + if (ts.isIdentifier(enumMember.initializer)) { + const [isEnumMember, valueName] = tsHelper.isEnumMember(enumDeclaration, enumMember.initializer); + if (isEnumMember) { + if (ts.isIdentifier(valueName)) { + return this.transformConstEnumValue(enumType, valueName.text, tsOriginal); + } + } else { + return tstl.setNodeOriginal(this.transformExpression(enumMember.initializer), tsOriginal); + } + } else { + return tstl.setNodeOriginal(this.transformExpression(enumMember.initializer), tsOriginal); + } + } else { + let enumValue = 0; + for (const member of enumDeclaration.members) { + if (member === enumMember) { + return tstl.createNumericLiteral(enumValue, tsOriginal); + } + if (member.initializer === undefined) { + enumValue++; + } else if (ts.isNumericLiteral(member.initializer)) { + enumValue = Number(member.initializer.text) + 1; + } + } + + throw TSTLErrors.CouldNotFindEnumMember(enumDeclaration, memberName, tsOriginal); + } + } + throw TSTLErrors.CouldNotFindEnumMember(enumDeclaration, memberName, tsOriginal); + } + public transformStringCallExpression(node: ts.CallExpression): tstl.Expression { const expression = node.expression as ts.PropertyAccessExpression; const params = this.transformArguments(node.arguments); diff --git a/src/TSTLErrors.ts b/src/TSTLErrors.ts index b5719bc90..3b4f2e4c6 100644 --- a/src/TSTLErrors.ts +++ b/src/TSTLErrors.ts @@ -4,6 +4,11 @@ import {TranspileError} from "./TranspileError"; import {TSHelper as tsHelper} from "./TSHelper"; export class TSTLErrors { + public static CouldNotFindEnumMember = + (enumDeclaration: ts.EnumDeclaration, enumMember: string, node: ts.Node) => new TranspileError( + `Could not find ${enumMember} in ${enumDeclaration.name.text}`, node + ); + public static DefaultImportsNotSupported = (node: ts.Node) => new TranspileError(`Default Imports are not supported, please use named imports instead!`, node); diff --git a/test/unit/enum.spec.ts b/test/unit/enum.spec.ts index 9fbf263f1..6f4762bce 100644 --- a/test/unit/enum.spec.ts +++ b/test/unit/enum.spec.ts @@ -26,10 +26,10 @@ export class EnumTests { MEMBER_TWO = "test2" } - const valueOne = TestEnum.MEMBER_ONE; + const valueOne = TestEnum.MEMBER_TWO; `; - Expect(util.transpileString(testCode)).toBe(`local valueOne = "test";`); + Expect(util.transpileString(testCode)).toBe(`local valueOne = "test2";`); } @Test("Const enum without initializer") @@ -40,10 +40,10 @@ export class EnumTests { MEMBER_TWO } - const valueOne = TestEnum.MEMBER_ONE; + const valueOne = TestEnum.MEMBER_TWO; `; - Expect(util.transpileString(testCode)).toBe(`local valueOne = 0;`); + Expect(util.transpileString(testCode)).toBe(`local valueOne = 1;`); } @Test("Const enum without initializer in some values") From 699c49de25bbb84caa7c7228b759da167d121225 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 10 Feb 2019 22:10:06 +0100 Subject: [PATCH 3/5] Adjusted tests --- test/translation/lua/enum.lua | 3 +++ test/translation/lua/enumHeterogeneous.lua | 3 +++ test/translation/lua/enumString.lua | 3 +++ test/translation/lua/modulesNamespaceExportEnum.lua | 2 ++ 4 files changed, 11 insertions(+) diff --git a/test/translation/lua/enum.lua b/test/translation/lua/enum.lua index 7226d7339..ab48266d7 100644 --- a/test/translation/lua/enum.lua +++ b/test/translation/lua/enum.lua @@ -1,4 +1,7 @@ TestEnum = {}; TestEnum.val1 = 0; +TestEnum[0] = "val1"; TestEnum.val2 = 2; +TestEnum[2] = "val2"; TestEnum.val3 = 3; +TestEnum[3] = "val3"; diff --git a/test/translation/lua/enumHeterogeneous.lua b/test/translation/lua/enumHeterogeneous.lua index 6ae3489d3..fec3ab147 100644 --- a/test/translation/lua/enumHeterogeneous.lua +++ b/test/translation/lua/enumHeterogeneous.lua @@ -1,4 +1,7 @@ TestEnum = {}; TestEnum.val1 = 0; +TestEnum[0] = "val1"; TestEnum.val2 = 3; +TestEnum[3] = "val2"; TestEnum.val3 = "baz"; +TestEnum.baz = "val3"; diff --git a/test/translation/lua/enumString.lua b/test/translation/lua/enumString.lua index 0dcd94e4d..f35f146a6 100644 --- a/test/translation/lua/enumString.lua +++ b/test/translation/lua/enumString.lua @@ -1,4 +1,7 @@ TestEnum = {}; TestEnum.val1 = "foo"; +TestEnum.foo = "val1"; TestEnum.val2 = "bar"; +TestEnum.bar = "val2"; TestEnum.val3 = "baz"; +TestEnum.baz = "val3"; diff --git a/test/translation/lua/modulesNamespaceExportEnum.lua b/test/translation/lua/modulesNamespaceExportEnum.lua index 938fdd56b..5e3997f00 100644 --- a/test/translation/lua/modulesNamespaceExportEnum.lua +++ b/test/translation/lua/modulesNamespaceExportEnum.lua @@ -4,6 +4,8 @@ local test = exports.test; do test.TestEnum = {}; test.TestEnum.foo = "foo"; + test.TestEnum.foo = "foo"; + test.TestEnum.bar = "bar"; test.TestEnum.bar = "bar"; end return exports; From 1c3b787dd54772ff497b1899bff5c8189bd6b0f6 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 10 Feb 2019 23:03:45 +0100 Subject: [PATCH 4/5] Added some tests for identifier values in const enums --- test/unit/enum.spec.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/unit/enum.spec.ts b/test/unit/enum.spec.ts index 6f4762bce..d21b695c4 100644 --- a/test/unit/enum.spec.ts +++ b/test/unit/enum.spec.ts @@ -158,4 +158,34 @@ export class EnumTests { Expect(result).toBe(1); } + + @Test("Const enum index identifier value") + public constEnumIndexIdnetifierValue(): void { + const result = util.transpileAndExecute( + `const enum testEnum { + abc, + def = 4, + ghi, + jkl = ghi + } + return testEnum["jkl"];` + ); + + Expect(result).toBe(5); + } + + @Test("Const enum index identifier chain") + public constEnumIndexIdnetifierChain(): void { + const result = util.transpileAndExecute( + `const enum testEnum { + abc = 3, + def, + ghi = def, + jkl = ghi, + } + return testEnum["ghi"];` + ); + + Expect(result).toBe(4); + } } From 52b8ff046349606c88e0b8d6a7dc9a6b45e8fec0 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Mon, 11 Feb 2019 18:09:20 +0100 Subject: [PATCH 5/5] Changed some enum value createXLiteral calls to transform calls --- src/LuaTransformer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 9167582bc..fa3cbabda 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -1011,13 +1011,13 @@ export class LuaTransformer { if (ts.isNumericLiteral(member.initializer)) { numericValue = Number(member.initializer.text); - valueExpression = tstl.createNumericLiteral(numericValue); + valueExpression = this.transformNumericLiteral(member.initializer); numericValue++; } else if (ts.isStringLiteral(member.initializer)) { hasStringInitializers = true; - valueExpression = tstl.createStringLiteral(member.initializer.text); + valueExpression = this.transformStringLiteral(member.initializer); } else {