Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ export namespace CommonNames {
export const ASC_VERSION_MAJOR = "ASC_VERSION_MAJOR";
export const ASC_VERSION_MINOR = "ASC_VERSION_MINOR";
export const ASC_VERSION_PATCH = "ASC_VERSION_PATCH";
// enums
export const EnumToString = "__enum_to_string";
// classes
export const I8 = "I8";
export const I16 = "I16";
Expand Down
49 changes: 48 additions & 1 deletion src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1522,6 +1522,43 @@ export class Compiler extends DiagnosticEmitter {
return true;
}

private ensureEnumToString(enumElement: Enum, reportNode: Node): string | null {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a couple blank lines in here? It's a little hard to read

if (!this.compileEnum(enumElement)) return null;
if (enumElement.is(CommonFlags.Const)) {
this.errorRelated(
DiagnosticCode.A_const_enum_member_can_only_be_accessed_using_a_string_literal,
reportNode.range, enumElement.identifierNode.range
);
return null;
}
let members = enumElement.members;
if (!members) return null;
if (enumElement.toStringFunctionName) return enumElement.toStringFunctionName;
const functionName = `${enumElement.internalName}#${CommonNames.EnumToString}`;
enumElement.toStringFunctionName = functionName;
const isInline = enumElement.hasDecorator(DecoratorFlags.Inline);
let module = this.module;
let exprs = new Array<ExpressionRef>();
// when the values are the same, TS returns the last enum value name that appears
for (let _keys = Map_keys(members), _values = Map_values(members), i = 1, k = _keys.length; i <= k; ++i) {
let enumValueName = unchecked(_keys[k - i]);
let member = unchecked(_values[k - i]);
Comment on lines +1543 to +1545
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (let _keys = Map_keys(members), _values = Map_values(members), i = 1, k = _keys.length; i <= k; ++i) {
let enumValueName = unchecked(_keys[k - i]);
let member = unchecked(_values[k - i]);
for (let _keys = Map_keys(members), _values = Map_values(members), i = _keys.length - 1; i >= 0; --i) {
let enumValueName = unchecked(_keys[i]);
let member = unchecked(_values[i]);

Doesn't this work?

if (member.kind != ElementKind.EnumValue) continue;
let enumValue = <EnumValue>member;
const enumValueExpr = isInline
? module.i32(i64_low(enumValue.constantIntegerValue))
: module.global_get(enumValue.internalName, TypeRef.I32);
let expr = module.if(
module.binary(BinaryOp.EqI32, enumValueExpr, module.local_get(0, TypeRef.I32)),
module.return(this.ensureStaticString(enumValueName))
);
exprs.push(expr);
}
exprs.push(module.unreachable());
module.addFunction(functionName, TypeRef.I32, TypeRef.I32, null, module.block(null, exprs, TypeRef.I32));
return functionName;
}

// === Functions ================================================================================

/** Compiles a priorly resolved function. */
Expand Down Expand Up @@ -7092,7 +7129,17 @@ export class Compiler extends DiagnosticEmitter {
): ExpressionRef {
let module = this.module;
let targetExpression = expression.expression;
let targetType = this.resolver.resolveExpression(targetExpression, this.currentFlow); // reports
let resolver = this.resolver;
let targetElement = resolver.lookupExpression(targetExpression, this.currentFlow, Type.auto, ReportMode.Swallow);
if (targetElement && targetElement.kind == ElementKind.Enum) {
const elementExpr = this.compileExpression(expression.elementExpression, Type.i32, Constraints.ConvImplicit);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we're allowing Enum[Enum.X] but not Enum["X"] (only Enum.X, the property access expression, is allowed). This is likely a good thing to have, unless users really want to get enum members with computed string values (which should probably be discouraged regardless).

(It also seems like we don't have a good error for someObject["property"] and we use TS2329 instead, so a good error for Enum["X"] isn't necessary for now.)

const toStringFunctionName = this.ensureEnumToString(<Enum>targetElement, expression);
this.currentType = this.program.stringInstance.type;
if (toStringFunctionName == null) return module.unreachable();
return module.call(toStringFunctionName, [ elementExpr ], TypeRef.I32);
}

let targetType = resolver.resolveExpression(targetExpression, this.currentFlow);
if (targetType) {
let classReference = targetType.getClassOrWrapper(this.program);
if (classReference) {
Expand Down
1 change: 1 addition & 0 deletions src/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@
"Type '{0}' has no property '{1}'.": 2460,
"The '{0}' operator cannot be applied to type '{1}'.": 2469,
"In 'const' enum declarations member initializer must be constant expression.": 2474,
"A const enum member can only be accessed using a string literal.": 2476,
"Export declaration conflicts with exported declaration of '{0}'.": 2484,
"'{0}' is referenced directly or indirectly in its own base expression.": 2506,
"Cannot create an instance of an abstract class.": 2511,
Expand Down
2 changes: 1 addition & 1 deletion src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3449,7 +3449,7 @@ export class Namespace extends DeclaredElement {

/** An enum. */
export class Enum extends TypedElement {

toStringFunctionName: string | null = null;
/** Constructs a new enum. */
constructor(
/** Simple name. */
Expand Down
7 changes: 7 additions & 0 deletions tests/compiler/enum-to-string-error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"asc_flags": [],
"stderr": [
"TS2476: A const enum member can only be accessed using a string literal.",
"EOF"
]
}
9 changes: 9 additions & 0 deletions tests/compiler/enum-to-string-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const enum CE {
CE0,
CE1,
CE2,
}

assert(CE[CE.CE0] === "CE0");

ERROR("EOF");
Loading
Loading