Skip to content

Commit fa3d9f3

Browse files
committed
align behavior of constant expressions in initializers of ambient enum members with spec
1 parent 5a77d67 commit fa3d9f3

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
@@ -12940,26 +12940,41 @@ namespace ts {
1294012940
if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) {
1294112941
let enumSymbol = getSymbolOfNode(node);
1294212942
let enumType = getDeclaredTypeOfSymbol(enumSymbol);
12943-
let autoValue = 0;
12943+
let autoValue = 0; // set to undefined when enum member is non-constant
1294412944
let ambient = isInAmbientContext(node);
1294512945
let enumIsConst = isConst(node);
1294612946

12947-
forEach(node.members, member => {
12948-
if (member.name.kind !== SyntaxKind.ComputedPropertyName && isNumericLiteralName((<Identifier>member.name).text)) {
12947+
for (const member of node.members) {
12948+
if (member.name.kind === SyntaxKind.ComputedPropertyName) {
12949+
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
12950+
}
12951+
else if (isNumericLiteralName((<Identifier>member.name).text)) {
1294912952
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
1295012953
}
12954+
12955+
const previousEnumMemberIsNonConstant = autoValue === undefined;
12956+
1295112957
let initializer = member.initializer;
1295212958
if (initializer) {
1295312959
autoValue = computeConstantValueForEnumMemberInitializer(initializer, enumType, enumIsConst, ambient);
1295412960
}
1295512961
else if (ambient && !enumIsConst) {
12962+
// In ambient enum declarations that specify no const modifier, enum member declarations
12963+
// that omit a value are considered computed members (as opposed to having auto-incremented values assigned).
1295612964
autoValue = undefined;
1295712965
}
12966+
else if (previousEnumMemberIsNonConstant) {
12967+
// If the member declaration specifies no value, the member is considered a constant enum member.
12968+
// If the member is the first member in the enum declaration, it is assigned the value zero.
12969+
// Otherwise, it is assigned the value of the immediately preceding member plus one,
12970+
// and an error occurs if the immediately preceding member is not a constant enum member
12971+
error(member.name, Diagnostics.Enum_member_must_have_initializer);
12972+
}
1295812973

1295912974
if (autoValue !== undefined) {
1296012975
getNodeLinks(member).enumMemberValue = autoValue++;
1296112976
}
12962-
});
12977+
}
1296312978

1296412979
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
1296512980
}
@@ -12975,11 +12990,11 @@ namespace ts {
1297512990
if (enumIsConst) {
1297612991
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
1297712992
}
12978-
else if (!ambient) {
12993+
else if (ambient) {
12994+
error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression);
12995+
}
12996+
else {
1297912997
// Only here do we need to check that the initializer is assignable to the enum type.
12980-
// If it is a constant value (not undefined), it is syntactically constrained to be a number.
12981-
// Also, we do not need to check this for ambients because there is already
12982-
// a syntax error if it is not a constant.
1298312998
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*headMessage*/ undefined);
1298412999
}
1298513000
}
@@ -13119,7 +13134,7 @@ namespace ts {
1311913134
}
1312013135

1312113136
// Grammar checking
13122-
checkGrammarDecorators(node) || checkGrammarModifiers(node) || checkGrammarEnumDeclaration(node);
13137+
checkGrammarDecorators(node) || checkGrammarModifiers(node);
1312313138

1312413139
checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0);
1312513140
checkCollisionWithCapturedThisVariable(node, node.name);
@@ -15619,40 +15634,6 @@ namespace ts {
1561915634
return false;
1562015635
}
1562115636

15622-
function checkGrammarEnumDeclaration(enumDecl: EnumDeclaration): boolean {
15623-
let enumIsConst = (enumDecl.flags & NodeFlags.Const) !== 0;
15624-
15625-
let hasError = false;
15626-
15627-
// skip checks below for const enums - they allow arbitrary initializers as long as they can be evaluated to constant expressions.
15628-
// since all values are known in compile time - it is not necessary to check that constant enum section precedes computed enum members.
15629-
if (!enumIsConst) {
15630-
let inConstantEnumMemberSection = true;
15631-
let inAmbientContext = isInAmbientContext(enumDecl);
15632-
for (let node of enumDecl.members) {
15633-
// Do not use hasDynamicName here, because that returns false for well known symbols.
15634-
// We want to perform checkComputedPropertyName for all computed properties, including
15635-
// well known symbols.
15636-
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
15637-
hasError = grammarErrorOnNode(node.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
15638-
}
15639-
else if (inAmbientContext) {
15640-
if (node.initializer && !isIntegerLiteral(node.initializer)) {
15641-
hasError = grammarErrorOnNode(node.name, Diagnostics.Ambient_enum_elements_can_only_have_integer_literal_initializers) || hasError;
15642-
}
15643-
}
15644-
else if (node.initializer) {
15645-
inConstantEnumMemberSection = isIntegerLiteral(node.initializer);
15646-
}
15647-
else if (!inConstantEnumMemberSection) {
15648-
hasError = grammarErrorOnNode(node.name, Diagnostics.Enum_member_must_have_initializer) || hasError;
15649-
}
15650-
}
15651-
}
15652-
15653-
return hasError;
15654-
}
15655-
1565615637
function hasParseDiagnostics(sourceFile: SourceFile): boolean {
1565715638
return sourceFile.parseDiagnostics.length > 0;
1565815639
}

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)