Skip to content

Commit 8257b1c

Browse files
authored
feat: support element access of enum (#2950)
1 parent 6087191 commit 8257b1c

File tree

10 files changed

+1141
-2
lines changed

10 files changed

+1141
-2
lines changed

src/common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ export namespace CommonNames {
194194
export const ASC_VERSION_MAJOR = "ASC_VERSION_MAJOR";
195195
export const ASC_VERSION_MINOR = "ASC_VERSION_MINOR";
196196
export const ASC_VERSION_PATCH = "ASC_VERSION_PATCH";
197+
// enums
198+
export const EnumToString = "__enum_to_string";
197199
// classes
198200
export const I8 = "I8";
199201
export const I16 = "I16";

src/compiler.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1522,6 +1522,49 @@ export class Compiler extends DiagnosticEmitter {
15221522
return true;
15231523
}
15241524

1525+
private ensureEnumToString(enumElement: Enum, reportNode: Node): string | null {
1526+
if (enumElement.toStringFunctionName) return enumElement.toStringFunctionName;
1527+
1528+
if (!this.compileEnum(enumElement)) return null;
1529+
if (enumElement.is(CommonFlags.Const)) {
1530+
this.errorRelated(
1531+
DiagnosticCode.A_const_enum_member_can_only_be_accessed_using_a_string_literal,
1532+
reportNode.range, enumElement.identifierNode.range
1533+
);
1534+
return null;
1535+
}
1536+
1537+
let members = enumElement.members;
1538+
if (!members) return null;
1539+
1540+
let module = this.module;
1541+
const isInline = enumElement.hasDecorator(DecoratorFlags.Inline);
1542+
1543+
const functionName = `${enumElement.internalName}#${CommonNames.EnumToString}`;
1544+
enumElement.toStringFunctionName = functionName;
1545+
1546+
let exprs = new Array<ExpressionRef>();
1547+
// when the values are the same, TS returns the last enum value name that appears
1548+
for (let _keys = Map_keys(members), _values = Map_values(members), i = 1, k = _keys.length; i <= k; ++i) {
1549+
let enumValueName = unchecked(_keys[k - i]);
1550+
let member = unchecked(_values[k - i]);
1551+
if (member.kind != ElementKind.EnumValue) continue;
1552+
let enumValue = <EnumValue>member;
1553+
const enumValueExpr = isInline
1554+
? module.i32(i64_low(enumValue.constantIntegerValue))
1555+
: module.global_get(enumValue.internalName, TypeRef.I32);
1556+
let expr = module.if(
1557+
module.binary(BinaryOp.EqI32, enumValueExpr, module.local_get(0, TypeRef.I32)),
1558+
module.return(this.ensureStaticString(enumValueName))
1559+
);
1560+
exprs.push(expr);
1561+
}
1562+
exprs.push(module.unreachable());
1563+
module.addFunction(functionName, TypeRef.I32, TypeRef.I32, null, module.block(null, exprs, TypeRef.I32));
1564+
1565+
return functionName;
1566+
}
1567+
15251568
// === Functions ================================================================================
15261569

15271570
/** Compiles a priorly resolved function. */
@@ -7092,7 +7135,17 @@ export class Compiler extends DiagnosticEmitter {
70927135
): ExpressionRef {
70937136
let module = this.module;
70947137
let targetExpression = expression.expression;
7095-
let targetType = this.resolver.resolveExpression(targetExpression, this.currentFlow); // reports
7138+
let resolver = this.resolver;
7139+
let targetElement = resolver.lookupExpression(targetExpression, this.currentFlow, Type.auto, ReportMode.Swallow);
7140+
if (targetElement && targetElement.kind == ElementKind.Enum) {
7141+
const elementExpr = this.compileExpression(expression.elementExpression, Type.i32, Constraints.ConvImplicit);
7142+
const toStringFunctionName = this.ensureEnumToString(<Enum>targetElement, expression);
7143+
this.currentType = this.program.stringInstance.type;
7144+
if (toStringFunctionName == null) return module.unreachable();
7145+
return module.call(toStringFunctionName, [ elementExpr ], TypeRef.I32);
7146+
}
7147+
7148+
let targetType = resolver.resolveExpression(targetExpression, this.currentFlow);
70967149
if (targetType) {
70977150
let classReference = targetType.getClassOrWrapper(this.program);
70987151
if (classReference) {

src/diagnosticMessages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@
174174
"Type '{0}' has no property '{1}'.": 2460,
175175
"The '{0}' operator cannot be applied to type '{1}'.": 2469,
176176
"In 'const' enum declarations member initializer must be constant expression.": 2474,
177+
"A const enum member can only be accessed using a string literal.": 2476,
177178
"Export declaration conflicts with exported declaration of '{0}'.": 2484,
178179
"'{0}' is referenced directly or indirectly in its own base expression.": 2506,
179180
"Cannot create an instance of an abstract class.": 2511,

src/program.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3449,7 +3449,7 @@ export class Namespace extends DeclaredElement {
34493449

34503450
/** An enum. */
34513451
export class Enum extends TypedElement {
3452-
3452+
toStringFunctionName: string | null = null;
34533453
/** Constructs a new enum. */
34543454
constructor(
34553455
/** Simple name. */
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"asc_flags": [],
3+
"stderr": [
4+
"TS2476: A const enum member can only be accessed using a string literal.",
5+
"EOF"
6+
]
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const enum CE {
2+
CE0,
3+
CE1,
4+
CE2,
5+
}
6+
7+
assert(CE[CE.CE0] === "CE0");
8+
9+
ERROR("EOF");

0 commit comments

Comments
 (0)