Skip to content

Commit 5140ca8

Browse files
authored
Fixed enums with identifier values not being supported. (#392)
* Enum identifier values * Fixed indexing of const enums * Adjusted tests * Added some tests for identifier values in const enums * Changed some enum value createXLiteral calls to transform calls
1 parent 03beabb commit 5140ca8

File tree

8 files changed

+228
-55
lines changed

8 files changed

+228
-55
lines changed

src/LuaTransformer.ts

Lines changed: 88 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,47 +1075,71 @@ export class LuaTransformer {
10751075
));
10761076
}
10771077
} else {
1078-
const table: tstl.IdentifierOrTableIndexExpression =
1079-
this.transformIdentifierExpression(enumDeclaration.name);
1080-
const property = tstl.createTableIndexExpression(table, memberName, undefined);
1078+
const enumTable = this.transformIdentifierExpression(enumDeclaration.name);
1079+
const property = tstl.createTableIndexExpression(enumTable, memberName);
10811080
result.push(tstl.createAssignmentStatement(property, enumMember.value, enumMember.original));
1081+
1082+
const valueIndex = tstl.createTableIndexExpression(enumTable, enumMember.value);
1083+
result.push(tstl.createAssignmentStatement(valueIndex, memberName, enumMember.original));
10821084
}
10831085
}
10841086

10851087
return result;
10861088
}
10871089

10881090
public computeEnumMembers(node: ts.EnumDeclaration):
1089-
Array<{name: ts.PropertyName, value: tstl.NumericLiteral | tstl.StringLiteral, original: ts.Node}> {
1091+
Array<{name: ts.PropertyName, value: tstl.Expression, original: ts.Node}> {
10901092
let numericValue = 0;
10911093
let hasStringInitializers = false;
10921094

1095+
const valueMap = new Map<ts.PropertyName, tstl.Expression>();
1096+
10931097
return node.members.map(member => {
1094-
let valueLiteral: tstl.NumericLiteral | tstl.StringLiteral;
1098+
let valueExpression: tstl.Expression;
10951099
if (member.initializer) {
1096-
if (ts.isNumericLiteral(member.initializer)) {
1100+
if (ts.isNumericLiteral(member.initializer))
1101+
{
10971102
numericValue = Number(member.initializer.text);
1098-
valueLiteral = tstl.createNumericLiteral(numericValue);
1099-
} else if (ts.isStringLiteral(member.initializer)) {
1103+
valueExpression = this.transformNumericLiteral(member.initializer);
1104+
numericValue++;
1105+
}
1106+
else if (ts.isStringLiteral(member.initializer))
1107+
{
11001108
hasStringInitializers = true;
1101-
valueLiteral = tstl.createStringLiteral(member.initializer.text);
1102-
} else {
1103-
throw TSTLErrors.InvalidEnumMember(member.initializer);
1109+
valueExpression = this.transformStringLiteral(member.initializer);
11041110
}
1105-
} else if (hasStringInitializers) {
1111+
else
1112+
{
1113+
if (ts.isIdentifier(member.initializer)) {
1114+
const [isEnumMember, originalName] = tsHelper.isEnumMember(node, member.initializer);
1115+
if (isEnumMember) {
1116+
valueExpression = valueMap.get(originalName);
1117+
} else {
1118+
valueExpression = this.transformExpression(member.initializer);
1119+
}
1120+
} else {
1121+
valueExpression = this.transformExpression(member.initializer);
1122+
}
1123+
}
1124+
}
1125+
else if (hasStringInitializers)
1126+
{
11061127
throw TSTLErrors.HeterogeneousEnum(node);
1107-
} else {
1108-
valueLiteral = tstl.createNumericLiteral(numericValue);
11091128
}
1129+
else
1130+
{
1131+
valueExpression = tstl.createNumericLiteral(numericValue);
1132+
numericValue++;
1133+
}
1134+
1135+
valueMap.set(member.name, valueExpression);
11101136

11111137
const enumMember = {
11121138
name: member.name,
11131139
original: member,
1114-
value: valueLiteral,
1140+
value: valueExpression,
11151141
};
11161142

1117-
numericValue++;
1118-
11191143
return enumMember;
11201144
});
11211145
}
@@ -2980,26 +3004,7 @@ export class LuaTransformer {
29803004
}
29813005

29823006
if (type.symbol && (type.symbol.flags & ts.SymbolFlags.ConstEnum)) {
2983-
const propertyValueDeclaration = this.checker.getTypeAtLocation(node).symbol.valueDeclaration;
2984-
2985-
if (propertyValueDeclaration && propertyValueDeclaration.kind === ts.SyntaxKind.EnumMember) {
2986-
const enumMember = propertyValueDeclaration as ts.EnumMember;
2987-
2988-
if (enumMember.initializer) {
2989-
return this.transformExpression(enumMember.initializer);
2990-
} else {
2991-
const enumMembers = this.computeEnumMembers(enumMember.parent);
2992-
const memberPosition = enumMember.parent.members.indexOf(enumMember);
2993-
2994-
if (memberPosition === -1) {
2995-
throw TSTLErrors.UnsupportedProperty(type.symbol.name, property, node);
2996-
}
2997-
2998-
const value = tstl.cloneNode(enumMembers[memberPosition].value);
2999-
tstl.setNodeOriginal(value, enumMember);
3000-
return value;
3001-
}
3002-
}
3007+
return this.transformConstEnumValue(type, property, node);
30033008
}
30043009

30053010
this.checkForLuaLibType(type);
@@ -3095,6 +3100,13 @@ export class LuaTransformer {
30953100
const index = this.transformExpression(node.argumentExpression);
30963101

30973102
const type = this.checker.getTypeAtLocation(node.expression);
3103+
3104+
if (type.symbol && (type.symbol.flags & ts.SymbolFlags.ConstEnum)
3105+
&& ts.isStringLiteral(node.argumentExpression))
3106+
{
3107+
return this.transformConstEnumValue(type, node.argumentExpression.text, node);
3108+
}
3109+
30983110
if (tsHelper.isArrayType(type, this.checker)) {
30993111
return tstl.createTableIndexExpression(table, this.expressionPlusOne(index), node);
31003112
} else if (tsHelper.isStringType(type)) {
@@ -3108,6 +3120,45 @@ export class LuaTransformer {
31083120
}
31093121
}
31103122

3123+
private transformConstEnumValue(enumType: ts.EnumType, memberName: string, tsOriginal: ts.Node): tstl.Expression {
3124+
// Assumption: the enum only has one declaration
3125+
const enumDeclaration = enumType.symbol.declarations.find(d => ts.isEnumDeclaration(d)) as ts.EnumDeclaration;
3126+
const enumMember = enumDeclaration.members
3127+
.find(m => ts.isIdentifier(m.name) && m.name.text === memberName);
3128+
3129+
if (enumMember) {
3130+
if (enumMember.initializer) {
3131+
if (ts.isIdentifier(enumMember.initializer)) {
3132+
const [isEnumMember, valueName] = tsHelper.isEnumMember(enumDeclaration, enumMember.initializer);
3133+
if (isEnumMember) {
3134+
if (ts.isIdentifier(valueName)) {
3135+
return this.transformConstEnumValue(enumType, valueName.text, tsOriginal);
3136+
}
3137+
} else {
3138+
return tstl.setNodeOriginal(this.transformExpression(enumMember.initializer), tsOriginal);
3139+
}
3140+
} else {
3141+
return tstl.setNodeOriginal(this.transformExpression(enumMember.initializer), tsOriginal);
3142+
}
3143+
} else {
3144+
let enumValue = 0;
3145+
for (const member of enumDeclaration.members) {
3146+
if (member === enumMember) {
3147+
return tstl.createNumericLiteral(enumValue, tsOriginal);
3148+
}
3149+
if (member.initializer === undefined) {
3150+
enumValue++;
3151+
} else if (ts.isNumericLiteral(member.initializer)) {
3152+
enumValue = Number(member.initializer.text) + 1;
3153+
}
3154+
}
3155+
3156+
throw TSTLErrors.CouldNotFindEnumMember(enumDeclaration, memberName, tsOriginal);
3157+
}
3158+
}
3159+
throw TSTLErrors.CouldNotFindEnumMember(enumDeclaration, memberName, tsOriginal);
3160+
}
3161+
31113162
public transformStringCallExpression(node: ts.CallExpression): tstl.Expression {
31123163
const expression = node.expression as ts.PropertyAccessExpression;
31133164
const params = this.transformArguments(node.arguments);

src/TSHelper.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,4 +625,21 @@ export class TSHelper {
625625
const firstDeclaration = this.getFirstDeclaration(symbol);
626626
return firstDeclaration === node;
627627
}
628+
629+
public static isEnumMember(enumDeclaration: ts.EnumDeclaration, value: ts.Expression): [boolean, ts.PropertyName] {
630+
if (ts.isIdentifier(value)) {
631+
const enumMember = enumDeclaration.members.find(m => ts.isIdentifier(m.name) && m.name.text === value.text);
632+
if (enumMember !== undefined) {
633+
if (enumMember.initializer && ts.isIdentifier(enumMember.initializer)) {
634+
return this.isEnumMember(enumDeclaration, enumMember.initializer);
635+
} else {
636+
return [true, enumMember.name];
637+
}
638+
} else {
639+
return [false, undefined];
640+
}
641+
} else {
642+
return [false, undefined];
643+
}
644+
}
628645
}

src/TSTLErrors.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import {TranspileError} from "./TranspileError";
44
import {TSHelper as tsHelper} from "./TSHelper";
55

66
export class TSTLErrors {
7+
public static CouldNotFindEnumMember =
8+
(enumDeclaration: ts.EnumDeclaration, enumMember: string, node: ts.Node) => new TranspileError(
9+
`Could not find ${enumMember} in ${enumDeclaration.name.text}`, node
10+
);
11+
712
public static DefaultImportsNotSupported = (node: ts.Node) =>
813
new TranspileError(`Default Imports are not supported, please use named imports instead!`, node);
914

test/translation/lua/enum.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
TestEnum = {};
22
TestEnum.val1 = 0;
3+
TestEnum[0] = "val1";
34
TestEnum.val2 = 2;
5+
TestEnum[2] = "val2";
46
TestEnum.val3 = 3;
7+
TestEnum[3] = "val3";
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
TestEnum = {};
22
TestEnum.val1 = 0;
3+
TestEnum[0] = "val1";
34
TestEnum.val2 = 3;
5+
TestEnum[3] = "val2";
46
TestEnum.val3 = "baz";
7+
TestEnum.baz = "val3";
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
TestEnum = {};
22
TestEnum.val1 = "foo";
3+
TestEnum.foo = "val1";
34
TestEnum.val2 = "bar";
5+
TestEnum.bar = "val2";
46
TestEnum.val3 = "baz";
7+
TestEnum.baz = "val3";

test/translation/lua/modulesNamespaceExportEnum.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ local test = exports.test;
44
do
55
test.TestEnum = {};
66
test.TestEnum.foo = "foo";
7+
test.TestEnum.foo = "foo";
8+
test.TestEnum.bar = "bar";
79
test.TestEnum.bar = "bar";
810
end
911
return exports;

test/unit/enum.spec.ts

Lines changed: 107 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ export class EnumTests {
2626
MEMBER_TWO = "test2"
2727
}
2828
29-
const valueOne = TestEnum.MEMBER_ONE;
29+
const valueOne = TestEnum.MEMBER_TWO;
3030
`;
3131

32-
Expect(util.transpileString(testCode)).toBe(`local valueOne = "test";`);
32+
Expect(util.transpileString(testCode)).toBe(`local valueOne = "test2";`);
3333
}
3434

3535
@Test("Const enum without initializer")
@@ -40,10 +40,10 @@ export class EnumTests {
4040
MEMBER_TWO
4141
}
4242
43-
const valueOne = TestEnum.MEMBER_ONE;
43+
const valueOne = TestEnum.MEMBER_TWO;
4444
`;
4545

46-
Expect(util.transpileString(testCode)).toBe(`local valueOne = 0;`);
46+
Expect(util.transpileString(testCode)).toBe(`local valueOne = 1;`);
4747
}
4848

4949
@Test("Const enum without initializer in some values")
@@ -76,20 +76,6 @@ export class EnumTests {
7676
+ "member values, or specify values (of the same type) for all members.");
7777
}
7878

79-
@Test("Unsuported enum")
80-
public unsuportedEnum(): void {
81-
// Transpile & Assert
82-
Expect(() => {
83-
const lua = util.transpileString(
84-
`enum TestEnum {
85-
val1 = [],
86-
val2 = "ok",
87-
val3 = "bye"
88-
}`
89-
);
90-
}).toThrowError(TranspileError, "Only numeric or string initializers allowed for enums.");
91-
}
92-
9379
@Test("String literal name in enum")
9480
public stringLiteralNameEnum(): void {
9581
const code = `enum TestEnum {
@@ -99,4 +85,107 @@ export class EnumTests {
9985
const result = util.transpileAndExecute(code);
10086
Expect(result).toBe("foo");
10187
}
88+
89+
@Test("Enum identifier value internal")
90+
public enumIdentifierValueInternal(): void {
91+
const result = util.transpileAndExecute(
92+
`enum testEnum {
93+
abc,
94+
def,
95+
ghi = def,
96+
jkl,
97+
}
98+
return \`\${testEnum.abc},\${testEnum.def},\${testEnum.ghi},\${testEnum.jkl}\`;`
99+
);
100+
101+
Expect(result).toBe("0,1,1,2");
102+
}
103+
104+
@Test("Enum identifier value internal recursive")
105+
public enumIdentifierValueInternalRecursive(): void {
106+
const result = util.transpileAndExecute(
107+
`enum testEnum {
108+
abc,
109+
def,
110+
ghi = def,
111+
jkl = ghi,
112+
}
113+
return \`\${testEnum.abc},\${testEnum.def},\${testEnum.ghi},\${testEnum.jkl}\`;`
114+
);
115+
116+
Expect(result).toBe("0,1,1,1");
117+
}
118+
119+
@Test("Enum identifier value external")
120+
public enumIdentifierValueExternal(): void {
121+
const result = util.transpileAndExecute(
122+
`const ext = 6;
123+
enum testEnum {
124+
abc,
125+
def,
126+
ghi = ext,
127+
}
128+
return \`\${testEnum.abc},\${testEnum.def},\${testEnum.ghi}\`;`
129+
);
130+
131+
Expect(result).toBe("0,1,6");
132+
}
133+
134+
@Test("Enum reverse mapping")
135+
public enumReverseMapping(): void {
136+
const result = util.transpileAndExecute(
137+
`enum testEnum {
138+
abc,
139+
def,
140+
ghi
141+
}
142+
return testEnum[testEnum.abc] + testEnum[testEnum.ghi]`
143+
);
144+
145+
Expect(result).toBe("abcghi");
146+
}
147+
148+
@Test("Const enum index")
149+
public constEnumIndex(): void {
150+
const result = util.transpileAndExecute(
151+
`const enum testEnum {
152+
abc,
153+
def,
154+
ghi
155+
}
156+
return testEnum["def"];`
157+
);
158+
159+
Expect(result).toBe(1);
160+
}
161+
162+
@Test("Const enum index identifier value")
163+
public constEnumIndexIdnetifierValue(): void {
164+
const result = util.transpileAndExecute(
165+
`const enum testEnum {
166+
abc,
167+
def = 4,
168+
ghi,
169+
jkl = ghi
170+
}
171+
return testEnum["jkl"];`
172+
);
173+
174+
Expect(result).toBe(5);
175+
}
176+
177+
@Test("Const enum index identifier chain")
178+
public constEnumIndexIdnetifierChain(): void {
179+
const result = util.transpileAndExecute(
180+
`const enum testEnum {
181+
abc = 3,
182+
def,
183+
ghi = def,
184+
jkl = ghi,
185+
}
186+
return testEnum["ghi"];`
187+
);
188+
189+
Expect(result).toBe(4);
190+
}
102191
}

0 commit comments

Comments
 (0)