Skip to content

Allow to use a const in the initializer of a const enum #18887

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
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
37 changes: 27 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22289,7 +22289,7 @@ namespace ts {
}
}

function computeMemberValue(member: EnumMember, autoValue: number) {
function computeMemberValue(member: EnumMember, autoValue: number): string | number | undefined {
if (isComputedNonLiteralName(<PropertyName>member.name)) {
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
}
Expand Down Expand Up @@ -22318,7 +22318,7 @@ namespace ts {
return undefined;
}

function computeConstantValue(member: EnumMember): string | number {
function computeConstantValue(member: EnumMember): string | number | undefined {
const enumKind = getEnumKind(getSymbolOfNode(member.parent));
const isConstEnum = isConst(member.parent);
const initializer = member.initializer;
Expand All @@ -22335,18 +22335,18 @@ namespace ts {
return 0;
}
else if (isConstEnum) {
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
error(initializer, Diagnostics.In_const_enum_declarations_a_member_initializer_must_have_a_string_or_number_literal_type);
}
else if (member.parent.flags & NodeFlags.Ambient) {
error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression);
error(initializer, Diagnostics.In_ambient_enum_declarations_a_member_initializer_must_have_a_string_or_number_literal_type);
}
else {
// Only here do we need to check that the initializer is assignable to the enum type.
checkTypeAssignableTo(checkExpression(initializer), getDeclaredTypeOfSymbol(getSymbolOfNode(member.parent)), initializer, /*headMessage*/ undefined);
}
return value;

function evaluate(expr: Expression): string | number {
function evaluate(expr: Expression): string | number | undefined {
switch (expr.kind) {
case SyntaxKind.PrefixUnaryExpression:
const value = evaluate((<PrefixUnaryExpression>expr).operand);
Expand Down Expand Up @@ -22384,13 +22384,19 @@ namespace ts {
return +(<NumericLiteral>expr).text;
case SyntaxKind.ParenthesizedExpression:
return evaluate((<ParenthesizedExpression>expr).expression);
case SyntaxKind.Identifier:
return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), (<Identifier>expr).escapedText);
case SyntaxKind.Identifier: {
const id = expr as Identifier;
if (nodeIsMissing(expr)) {
return 0;
}
const fromEnum = evaluateEnumMember(id, getSymbolOfNode(member.parent), id.escapedText);
return fromEnum !== undefined ? fromEnum : getFromExpression(id);
}
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.PropertyAccessExpression:
const ex = <PropertyAccessExpression | ElementAccessExpression>expr;
if (isConstantMemberAccess(ex)) {
const type = getTypeOfExpression(ex.expression);
const type = checkExpression(ex.expression);
if (type.symbol && type.symbol.flags & SymbolFlags.Enum) {
let name: __String;
if (ex.kind === SyntaxKind.PropertyAccessExpression) {
Expand All @@ -22401,15 +22407,19 @@ namespace ts {
Debug.assert(isLiteralExpression(argument));
name = escapeLeadingUnderscores((argument as LiteralExpression).text);
}
return evaluateEnumMember(expr, type.symbol, name);
const fromEnum = evaluateEnumMember(ex, type.symbol, name);
if (fromEnum !== undefined) {
return fromEnum;
}
}
return getFromExpression(ex);
}
break;
}
return undefined;
}

function evaluateEnumMember(expr: Expression, enumSymbol: Symbol, name: __String) {
function evaluateEnumMember(expr: Identifier | ElementAccessExpression | PropertyAccessExpression, enumSymbol: Symbol, name: __String): string | number | undefined {
const memberSymbol = enumSymbol.exports.get(name);
if (memberSymbol) {
const declaration = memberSymbol.valueDeclaration;
Expand All @@ -22423,6 +22433,13 @@ namespace ts {
}
return undefined;
}

function getFromExpression(expr: Identifier | ElementAccessExpression | PropertyAccessExpression): string | number | undefined {
const type = checkExpression(expr);
if (type.flags & TypeFlags.StringOrNumberLiteral) {
return (type as LiteralType).value;
}
}
}

function isConstantMemberAccess(node: Expression): boolean {
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@
"category": "Error",
"code": 1064
},
"In ambient enum declarations member initializer must be constant expression.": {
"In ambient enum declarations, a member initializer must have a string or number literal type.": {
"category": "Error",
"code": 1066
},
Expand Down Expand Up @@ -1580,7 +1580,7 @@
"category": "Error",
"code": 2473
},
"In 'const' enum declarations member initializer must be constant expression.": {
"In 'const' enum declarations, a member initializer must have a string or number literal type.": {
"category": "Error",
"code": 2474
},
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/ambientEnum1.errors.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
tests/cases/compiler/ambientEnum1.ts(7,13): error TS1066: In ambient enum declarations member initializer must be constant expression.
tests/cases/compiler/ambientEnum1.ts(7,13): error TS1066: In ambient enum declarations, a member initializer must have a string or number literal type.


==== tests/cases/compiler/ambientEnum1.ts (1 errors) ====
Expand All @@ -10,5 +10,5 @@ tests/cases/compiler/ambientEnum1.ts(7,13): error TS1066: In ambient enum declar
declare enum E2 {
x = 'foo'.length
~~~~~~~~~~~~
!!! error TS1066: In ambient enum declarations member initializer must be constant expression.
!!! error TS1066: In ambient enum declarations, a member initializer must have a string or number literal type.
}
4 changes: 2 additions & 2 deletions tests/baselines/reference/ambientErrors.errors.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
tests/cases/conformance/ambient/ambientErrors.ts(2,15): error TS1039: Initializers are not allowed in ambient contexts.
tests/cases/conformance/ambient/ambientErrors.ts(17,22): error TS2371: A parameter initializer is only allowed in a function or constructor implementation.
tests/cases/conformance/ambient/ambientErrors.ts(20,24): error TS1183: An implementation cannot be declared in ambient contexts.
tests/cases/conformance/ambient/ambientErrors.ts(29,9): error TS1066: In ambient enum declarations member initializer must be constant expression.
tests/cases/conformance/ambient/ambientErrors.ts(29,9): error TS1066: In ambient enum declarations, a member initializer must have a string or number literal type.
tests/cases/conformance/ambient/ambientErrors.ts(34,11): error TS1039: Initializers are not allowed in ambient contexts.
tests/cases/conformance/ambient/ambientErrors.ts(35,19): error TS1183: An implementation cannot be declared in ambient contexts.
tests/cases/conformance/ambient/ambientErrors.ts(37,20): error TS1039: Initializers are not allowed in ambient contexts.
Expand Down Expand Up @@ -51,7 +51,7 @@ tests/cases/conformance/ambient/ambientErrors.ts(57,5): error TS2309: An export
declare enum E2 {
x = 'foo'.length
~~~~~~~~~~~~
!!! error TS1066: In ambient enum declarations member initializer must be constant expression.
!!! error TS1066: In ambient enum declarations, a member initializer must have a string or number literal type.
}

// Ambient module with initializers for values, bodies for functions / classes
Expand Down
12 changes: 6 additions & 6 deletions tests/baselines/reference/constEnum2.errors.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
tests/cases/conformance/constEnums/constEnum2.ts(10,9): error TS2474: In 'const' enum declarations member initializer must be constant expression.
tests/cases/conformance/constEnums/constEnum2.ts(11,9): error TS2474: In 'const' enum declarations member initializer must be constant expression.
tests/cases/conformance/constEnums/constEnum2.ts(10,9): error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.
tests/cases/conformance/constEnums/constEnum2.ts(11,9): error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.
tests/cases/conformance/constEnums/constEnum2.ts(12,5): error TS1005: ',' expected.
tests/cases/conformance/constEnums/constEnum2.ts(12,9): error TS2474: In 'const' enum declarations member initializer must be constant expression.
tests/cases/conformance/constEnums/constEnum2.ts(12,9): error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.


==== tests/cases/conformance/constEnums/constEnum2.ts (4 errors) ====
Expand All @@ -16,13 +16,13 @@ tests/cases/conformance/constEnums/constEnum2.ts(12,9): error TS2474: In 'const'
d = 10,
e = 199 * Math.floor(Math.random() * 1000),
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2474: In 'const' enum declarations member initializer must be constant expression.
!!! error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.
f = d - (100 * Math.floor(Math.random() % 8))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2474: In 'const' enum declarations member initializer must be constant expression.
!!! error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.
g = CONST,
~
!!! error TS1005: ',' expected.
~~~~~
!!! error TS2474: In 'const' enum declarations member initializer must be constant expression.
!!! error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.
}
16 changes: 11 additions & 5 deletions tests/baselines/reference/constEnumErrors.errors.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
tests/cases/compiler/constEnumErrors.ts(1,12): error TS2300: Duplicate identifier 'E'.
tests/cases/compiler/constEnumErrors.ts(5,8): error TS2300: Duplicate identifier 'E'.
tests/cases/compiler/constEnumErrors.ts(12,9): error TS2651: A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums.
tests/cases/compiler/constEnumErrors.ts(14,9): error TS2474: In 'const' enum declarations member initializer must be constant expression.
tests/cases/compiler/constEnumErrors.ts(15,10): error TS2474: In 'const' enum declarations member initializer must be constant expression.
tests/cases/compiler/constEnumErrors.ts(14,9): error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.
tests/cases/compiler/constEnumErrors.ts(14,12): error TS2339: Property 'Z' does not exist on type 'typeof E1'.
tests/cases/compiler/constEnumErrors.ts(15,10): error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.
tests/cases/compiler/constEnumErrors.ts(15,13): error TS2339: Property 'Z' does not exist on type 'typeof E1'.
tests/cases/compiler/constEnumErrors.ts(22,13): error TS2476: A const enum member can only be accessed using a string literal.
tests/cases/compiler/constEnumErrors.ts(24,13): error TS2476: A const enum member can only be accessed using a string literal.
tests/cases/compiler/constEnumErrors.ts(26,9): error TS2475: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment.
Expand All @@ -13,7 +15,7 @@ tests/cases/compiler/constEnumErrors.ts(41,9): error TS2477: 'const' enum member
tests/cases/compiler/constEnumErrors.ts(42,9): error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'.


==== tests/cases/compiler/constEnumErrors.ts (13 errors) ====
==== tests/cases/compiler/constEnumErrors.ts (15 errors) ====
const enum E {
~
!!! error TS2300: Duplicate identifier 'E'.
Expand All @@ -35,10 +37,14 @@ tests/cases/compiler/constEnumErrors.ts(42,9): error TS2478: 'const' enum member
// forward reference to the element of the same enum
Y = E1.Z,
~~~~
!!! error TS2474: In 'const' enum declarations member initializer must be constant expression.
!!! error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.
~
!!! error TS2339: Property 'Z' does not exist on type 'typeof E1'.
Y1 = E1["Z"]
~~~~~~~
!!! error TS2474: In 'const' enum declarations member initializer must be constant expression.
!!! error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.
~~~
!!! error TS2339: Property 'Z' does not exist on type 'typeof E1'.
}

const enum E2 {
Expand Down
26 changes: 26 additions & 0 deletions tests/baselines/reference/enumInitializers_const.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//// [enumInitializers_const.ts]
const stride = 5;
namespace N {
export const two = 2;
}
const enum E {
x = stride * N.two,
}
E.x;

const s = "abc";
const enum S {
abc = s,
}
S.abc;


//// [enumInitializers_const.js]
var stride = 5;
var N;
(function (N) {
N.two = 2;
})(N || (N = {}));
10 /* x */;
var s = "abc";
"abc" /* abc */;
40 changes: 40 additions & 0 deletions tests/baselines/reference/enumInitializers_const.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
=== tests/cases/compiler/enumInitializers_const.ts ===
const stride = 5;
>stride : Symbol(stride, Decl(enumInitializers_const.ts, 0, 5))

namespace N {
>N : Symbol(N, Decl(enumInitializers_const.ts, 0, 17))

export const two = 2;
>two : Symbol(two, Decl(enumInitializers_const.ts, 2, 16))
}
const enum E {
>E : Symbol(E, Decl(enumInitializers_const.ts, 3, 1))

x = stride * N.two,
>x : Symbol(E.x, Decl(enumInitializers_const.ts, 4, 14))
>stride : Symbol(stride, Decl(enumInitializers_const.ts, 0, 5))
>N.two : Symbol(N.two, Decl(enumInitializers_const.ts, 2, 16))
>N : Symbol(N, Decl(enumInitializers_const.ts, 0, 17))
>two : Symbol(N.two, Decl(enumInitializers_const.ts, 2, 16))
}
E.x;
>E.x : Symbol(E.x, Decl(enumInitializers_const.ts, 4, 14))
>E : Symbol(E, Decl(enumInitializers_const.ts, 3, 1))
>x : Symbol(E.x, Decl(enumInitializers_const.ts, 4, 14))

const s = "abc";
>s : Symbol(s, Decl(enumInitializers_const.ts, 9, 5))

const enum S {
>S : Symbol(S, Decl(enumInitializers_const.ts, 9, 16))

abc = s,
>abc : Symbol(S.abc, Decl(enumInitializers_const.ts, 10, 14))
>s : Symbol(s, Decl(enumInitializers_const.ts, 9, 5))
}
S.abc;
>S.abc : Symbol(S.abc, Decl(enumInitializers_const.ts, 10, 14))
>S : Symbol(S, Decl(enumInitializers_const.ts, 9, 16))
>abc : Symbol(S.abc, Decl(enumInitializers_const.ts, 10, 14))

44 changes: 44 additions & 0 deletions tests/baselines/reference/enumInitializers_const.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
=== tests/cases/compiler/enumInitializers_const.ts ===
const stride = 5;
>stride : 5
>5 : 5

namespace N {
>N : typeof N

export const two = 2;
>two : 2
>2 : 2
}
const enum E {
>E : E

x = stride * N.two,
>x : E
>stride * N.two : number
>stride : 5
>N.two : 2
>N : typeof N
>two : 2
}
E.x;
>E.x : E
>E : typeof E
>x : E

const s = "abc";
>s : "abc"
>"abc" : "abc"

const enum S {
>S : S

abc = s,
>abc : S
>s : "abc"
}
S.abc;
>S.abc : S
>S : typeof S
>abc : S

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
tests/cases/compiler/enumInitializers_const_circular.ts(3,9): error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.
tests/cases/compiler/enumInitializers_const_circular.ts(10,9): error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.


==== tests/cases/compiler/enumInitializers_const_circular.ts (2 errors) ====
const x = E.y;
const enum E {
y = x,
~
!!! error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.
}

namespace N {
export const a = F.a;
}
const enum F {
a = N.a,
~~~
!!! error TS2474: In 'const' enum declarations, a member initializer must have a string or number literal type.
}

20 changes: 20 additions & 0 deletions tests/baselines/reference/enumInitializers_const_circular.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//// [enumInitializers_const_circular.ts]
const x = E.y;
const enum E {
y = x,
}

namespace N {
export const a = F.a;
}
const enum F {
a = N.a,
}


//// [enumInitializers_const_circular.js]
var x = E.y;
var N;
(function (N) {
N.a = F.a;
})(N || (N = {}));
Loading