Skip to content

Commit b866781

Browse files
committed
Merge pull request #4946 from Microsoft/constInitializersInEnums
align behavior of constant expressions in initializers of ambient enu…
2 parents b786687 + fa3d9f3 commit b866781

27 files changed

+368
-161
lines changed

src/compiler/checker.ts

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12962,26 +12962,41 @@ namespace ts {
1296212962
if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) {
1296312963
let enumSymbol = getSymbolOfNode(node);
1296412964
let enumType = getDeclaredTypeOfSymbol(enumSymbol);
12965-
let autoValue = 0;
12965+
let autoValue = 0; // set to undefined when enum member is non-constant
1296612966
let ambient = isInAmbientContext(node);
1296712967
let enumIsConst = isConst(node);
1296812968

12969-
forEach(node.members, member => {
12970-
if (member.name.kind !== SyntaxKind.ComputedPropertyName && isNumericLiteralName((<Identifier>member.name).text)) {
12969+
for (const member of node.members) {
12970+
if (member.name.kind === SyntaxKind.ComputedPropertyName) {
12971+
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
12972+
}
12973+
else if (isNumericLiteralName((<Identifier>member.name).text)) {
1297112974
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
1297212975
}
12976+
12977+
const previousEnumMemberIsNonConstant = autoValue === undefined;
12978+
1297312979
let initializer = member.initializer;
1297412980
if (initializer) {
1297512981
autoValue = computeConstantValueForEnumMemberInitializer(initializer, enumType, enumIsConst, ambient);
1297612982
}
1297712983
else if (ambient && !enumIsConst) {
12984+
// In ambient enum declarations that specify no const modifier, enum member declarations
12985+
// that omit a value are considered computed members (as opposed to having auto-incremented values assigned).
1297812986
autoValue = undefined;
1297912987
}
12988+
else if (previousEnumMemberIsNonConstant) {
12989+
// If the member declaration specifies no value, the member is considered a constant enum member.
12990+
// If the member is the first member in the enum declaration, it is assigned the value zero.
12991+
// Otherwise, it is assigned the value of the immediately preceding member plus one,
12992+
// and an error occurs if the immediately preceding member is not a constant enum member
12993+
error(member.name, Diagnostics.Enum_member_must_have_initializer);
12994+
}
1298012995

1298112996
if (autoValue !== undefined) {
1298212997
getNodeLinks(member).enumMemberValue = autoValue++;
1298312998
}
12984-
});
12999+
}
1298513000

1298613001
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
1298713002
}
@@ -12997,11 +13012,11 @@ namespace ts {
1299713012
if (enumIsConst) {
1299813013
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
1299913014
}
13000-
else if (!ambient) {
13015+
else if (ambient) {
13016+
error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression);
13017+
}
13018+
else {
1300113019
// Only here do we need to check that the initializer is assignable to the enum type.
13002-
// If it is a constant value (not undefined), it is syntactically constrained to be a number.
13003-
// Also, we do not need to check this for ambients because there is already
13004-
// a syntax error if it is not a constant.
1300513020
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*headMessage*/ undefined);
1300613021
}
1300713022
}
@@ -13141,7 +13156,7 @@ namespace ts {
1314113156
}
1314213157

1314313158
// Grammar checking
13144-
checkGrammarDecorators(node) || checkGrammarModifiers(node) || checkGrammarEnumDeclaration(node);
13159+
checkGrammarDecorators(node) || checkGrammarModifiers(node);
1314513160

1314613161
checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0);
1314713162
checkCollisionWithCapturedThisVariable(node, node.name);
@@ -15641,40 +15656,6 @@ namespace ts {
1564115656
return false;
1564215657
}
1564315658

15644-
function checkGrammarEnumDeclaration(enumDecl: EnumDeclaration): boolean {
15645-
let enumIsConst = (enumDecl.flags & NodeFlags.Const) !== 0;
15646-
15647-
let hasError = false;
15648-
15649-
// skip checks below for const enums - they allow arbitrary initializers as long as they can be evaluated to constant expressions.
15650-
// since all values are known in compile time - it is not necessary to check that constant enum section precedes computed enum members.
15651-
if (!enumIsConst) {
15652-
let inConstantEnumMemberSection = true;
15653-
let inAmbientContext = isInAmbientContext(enumDecl);
15654-
for (let node of enumDecl.members) {
15655-
// Do not use hasDynamicName here, because that returns false for well known symbols.
15656-
// We want to perform checkComputedPropertyName for all computed properties, including
15657-
// well known symbols.
15658-
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
15659-
hasError = grammarErrorOnNode(node.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
15660-
}
15661-
else if (inAmbientContext) {
15662-
if (node.initializer && !isIntegerLiteral(node.initializer)) {
15663-
hasError = grammarErrorOnNode(node.name, Diagnostics.Ambient_enum_elements_can_only_have_integer_literal_initializers) || hasError;
15664-
}
15665-
}
15666-
else if (node.initializer) {
15667-
inConstantEnumMemberSection = isIntegerLiteral(node.initializer);
15668-
}
15669-
else if (!inConstantEnumMemberSection) {
15670-
hasError = grammarErrorOnNode(node.name, Diagnostics.Enum_member_must_have_initializer) || hasError;
15671-
}
15672-
}
15673-
}
15674-
15675-
return hasError;
15676-
}
15677-
1567815659
function hasParseDiagnostics(sourceFile: SourceFile): boolean {
1567915660
return sourceFile.parseDiagnostics.length > 0;
1568015661
}

src/compiler/diagnosticMessages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@
195195
"category": "Error",
196196
"code": 1063
197197
},
198-
"Ambient enum elements can only have integer literal initializers.": {
198+
"In ambient enum declarations member initializer must be constant expression.": {
199199
"category": "Error",
200200
"code": 1066
201201
},
Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1-
tests/cases/compiler/ambientEnum1.ts(2,9): error TS1066: Ambient enum elements can only have integer literal initializers.
2-
tests/cases/compiler/ambientEnum1.ts(7,9): error TS1066: Ambient enum elements can only have integer literal initializers.
1+
tests/cases/compiler/ambientEnum1.ts(7,13): error TS1066: In ambient enum declarations member initializer must be constant expression.
32

43

5-
==== tests/cases/compiler/ambientEnum1.ts (2 errors) ====
4+
==== tests/cases/compiler/ambientEnum1.ts (1 errors) ====
65
declare enum E1 {
76
y = 4.23
8-
~
9-
!!! error TS1066: Ambient enum elements can only have integer literal initializers.
107
}
118

129
// Ambient enum with computer member
1310
declare enum E2 {
1411
x = 'foo'.length
15-
~
16-
!!! error TS1066: Ambient enum elements can only have integer literal initializers.
12+
~~~~~~~~~~~~
13+
!!! error TS1066: In ambient enum declarations member initializer must be constant expression.
1714
}

tests/baselines/reference/ambientEnumDeclaration1.errors.txt

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
=== tests/cases/conformance/ambient/ambientEnumDeclaration1.ts ===
2+
// In ambient enum declarations, all values specified in enum member declarations must be classified as constant enum expressions.
3+
4+
declare enum E {
5+
>E : Symbol(E, Decl(ambientEnumDeclaration1.ts, 0, 0))
6+
7+
a = 10,
8+
>a : Symbol(E.a, Decl(ambientEnumDeclaration1.ts, 2, 16))
9+
10+
b = 10 + 1,
11+
>b : Symbol(E.b, Decl(ambientEnumDeclaration1.ts, 3, 11))
12+
13+
c = b,
14+
>c : Symbol(E.c, Decl(ambientEnumDeclaration1.ts, 4, 15))
15+
>b : Symbol(E.b, Decl(ambientEnumDeclaration1.ts, 3, 11))
16+
17+
d = (c) + 1,
18+
>d : Symbol(E.d, Decl(ambientEnumDeclaration1.ts, 5, 10))
19+
>c : Symbol(E.c, Decl(ambientEnumDeclaration1.ts, 4, 15))
20+
21+
e = 10 << 2 * 8,
22+
>e : Symbol(E.e, Decl(ambientEnumDeclaration1.ts, 6, 16))
23+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
=== tests/cases/conformance/ambient/ambientEnumDeclaration1.ts ===
2+
// In ambient enum declarations, all values specified in enum member declarations must be classified as constant enum expressions.
3+
4+
declare enum E {
5+
>E : E
6+
7+
a = 10,
8+
>a : E
9+
>10 : number
10+
11+
b = 10 + 1,
12+
>b : E
13+
>10 + 1 : number
14+
>10 : number
15+
>1 : number
16+
17+
c = b,
18+
>c : E
19+
>b : E
20+
21+
d = (c) + 1,
22+
>d : E
23+
>(c) + 1 : number
24+
>(c) : E
25+
>c : E
26+
>1 : number
27+
28+
e = 10 << 2 * 8,
29+
>e : E
30+
>10 << 2 * 8 : number
31+
>10 : number
32+
>2 * 8 : number
33+
>2 : number
34+
>8 : number
35+
}

tests/baselines/reference/ambientEnumElementInitializer3.errors.txt

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
=== tests/cases/compiler/ambientEnumElementInitializer3.ts ===
2+
declare enum E {
3+
>E : Symbol(E, Decl(ambientEnumElementInitializer3.ts, 0, 0))
4+
5+
e = 3.3 // Decimal
6+
>e : Symbol(E.e, Decl(ambientEnumElementInitializer3.ts, 0, 16))
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
=== tests/cases/compiler/ambientEnumElementInitializer3.ts ===
2+
declare enum E {
3+
>E : E
4+
5+
e = 3.3 // Decimal
6+
>e : E
7+
>3.3 : number
8+
}

tests/baselines/reference/ambientErrors.errors.txt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ tests/cases/conformance/ambient/ambientErrors.ts(2,15): error TS1039: Initialize
22
tests/cases/conformance/ambient/ambientErrors.ts(6,18): error TS2382: Specialized overload signature is not assignable to any non-specialized signature.
33
tests/cases/conformance/ambient/ambientErrors.ts(17,22): error TS2371: A parameter initializer is only allowed in a function or constructor implementation.
44
tests/cases/conformance/ambient/ambientErrors.ts(20,24): error TS1183: An implementation cannot be declared in ambient contexts.
5-
tests/cases/conformance/ambient/ambientErrors.ts(24,5): error TS1066: Ambient enum elements can only have integer literal initializers.
6-
tests/cases/conformance/ambient/ambientErrors.ts(29,5): error TS1066: Ambient enum elements can only have integer literal initializers.
5+
tests/cases/conformance/ambient/ambientErrors.ts(29,9): error TS1066: In ambient enum declarations member initializer must be constant expression.
76
tests/cases/conformance/ambient/ambientErrors.ts(34,11): error TS1039: Initializers are not allowed in ambient contexts.
87
tests/cases/conformance/ambient/ambientErrors.ts(35,19): error TS1183: An implementation cannot be declared in ambient contexts.
98
tests/cases/conformance/ambient/ambientErrors.ts(37,20): error TS1039: Initializers are not allowed in ambient contexts.
@@ -16,7 +15,7 @@ tests/cases/conformance/ambient/ambientErrors.ts(51,16): error TS2436: Ambient m
1615
tests/cases/conformance/ambient/ambientErrors.ts(57,5): error TS2309: An export assignment cannot be used in a module with other exported elements.
1716

1817

19-
==== tests/cases/conformance/ambient/ambientErrors.ts (16 errors) ====
18+
==== tests/cases/conformance/ambient/ambientErrors.ts (15 errors) ====
2019
// Ambient variable with an initializer
2120
declare var x = 4;
2221
~
@@ -49,15 +48,13 @@ tests/cases/conformance/ambient/ambientErrors.ts(57,5): error TS2309: An export
4948
// Ambient enum with non - integer literal constant member
5049
declare enum E1 {
5150
y = 4.23
52-
~
53-
!!! error TS1066: Ambient enum elements can only have integer literal initializers.
5451
}
5552

5653
// Ambient enum with computer member
5754
declare enum E2 {
5855
x = 'foo'.length
59-
~
60-
!!! error TS1066: Ambient enum elements can only have integer literal initializers.
56+
~~~~~~~~~~~~
57+
!!! error TS1066: In ambient enum declarations member initializer must be constant expression.
6158
}
6259

6360
// Ambient module with initializers for values, bodies for functions / classes

0 commit comments

Comments
 (0)