Skip to content

Commit 20229d4

Browse files
committed
Make ambiant & const enums report similar errors (allow any constant number expression). fixes microsoft#2790
If an invalid enum constant expression is found, continue incrementing with the last valid initialized value. If an enum expression references another enum member, then emit a reference to the other value.
1 parent ce98da3 commit 20229d4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+514
-212
lines changed

src/compiler/checker.ts

Lines changed: 20 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ module ts {
6868
getFullyQualifiedName,
6969
getResolvedSignature,
7070
getConstantValue,
71+
getTypeOfExpression,
7172
isValidPropertyAccess,
7273
getSignatureFromDeclaration,
7374
isImplementationOfOverload,
@@ -6402,7 +6403,7 @@ module ts {
64026403
let isConstEnum = isConstEnumObjectType(objectType);
64036404
if (isConstEnum &&
64046405
(!node.argumentExpression || node.argumentExpression.kind !== SyntaxKind.StringLiteral)) {
6405-
error(node.argumentExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal);
6406+
error(node.argumentExpression, Diagnostics.const_enum_member_can_only_be_accessed_using_a_string_literal);
64066407
return unknownType;
64076408
}
64086409

@@ -8081,7 +8082,7 @@ module ts {
80818082
((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(<Identifier>node));
80828083

80838084
if (!ok) {
8084-
error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment);
8085+
error(node, Diagnostics.const_enum_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment);
80858086
}
80868087
}
80878088
return type;
@@ -10269,6 +10270,7 @@ module ts {
1026910270
let enumSymbol = getSymbolOfNode(node);
1027010271
let enumType = getDeclaredTypeOfSymbol(enumSymbol);
1027110272
let autoValue = 0;
10273+
let initValue: number;
1027210274
let ambient = isInAmbientContext(node);
1027310275
let enumIsConst = isConst(node);
1027410276

@@ -10278,10 +10280,10 @@ module ts {
1027810280
}
1027910281
let initializer = member.initializer;
1028010282
if (initializer) {
10281-
autoValue = getConstantValueForEnumMemberInitializer(initializer);
10282-
if (autoValue === undefined) {
10283+
initValue = getConstantValueForEnumMemberInitializer(initializer);
10284+
if (initValue === undefined) {
1028310285
if (enumIsConst) {
10284-
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
10286+
error(initializer, Diagnostics.const_enum_initializer_must_be_a_constant_expression);
1028510287
}
1028610288
else if (!ambient) {
1028710289
// Only here do we need to check that the initializer is assignable to the enum type.
@@ -10290,19 +10292,18 @@ module ts {
1029010292
// a syntax error if it is not a constant.
1029110293
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*headMessage*/ undefined);
1029210294
}
10295+
return;
1029310296
}
1029410297
else if (enumIsConst) {
10295-
if (isNaN(autoValue)) {
10296-
error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN);
10298+
if (isNaN(initValue)) {
10299+
return error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN);
1029710300
}
10298-
else if (!isFinite(autoValue)) {
10299-
error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
10301+
else if (!isFinite(initValue)) {
10302+
return error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
1030010303
}
10304+
1030110305
}
10302-
10303-
}
10304-
else if (ambient && !enumIsConst) {
10305-
autoValue = undefined;
10306+
autoValue = initValue;
1030610307
}
1030710308

1030810309
if (autoValue !== undefined) {
@@ -10437,6 +10438,8 @@ module ts {
1043710438
return;
1043810439
}
1043910440

10441+
computeEnumMemberValues(node);
10442+
1044010443
// Grammar checking
1044110444
checkGrammarDeclarationNameInStrictMode(node) || checkGrammarDecorators(node) || checkGrammarModifiers(node) || checkGrammarEnumDeclaration(node);
1044210445

@@ -10445,8 +10448,6 @@ module ts {
1044510448
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
1044610449
checkExportsOnMergedDeclarations(node);
1044710450

10448-
computeEnumMemberValues(node);
10449-
1045010451
let enumIsConst = isConst(node);
1045110452
if (compilerOptions.separateCompilation && enumIsConst && isInAmbientContext(node)) {
1045210453
error(node.name, Diagnostics.Ambient_const_enums_are_not_allowed_when_the_separateCompilation_flag_is_provided);
@@ -11998,6 +11999,7 @@ module ts {
1199811999
isSymbolAccessible,
1199912000
isEntityNameVisible,
1200012001
getConstantValue,
12002+
getTypeOfExpression,
1200112003
resolvesToSomeValue,
1200212004
collectLinkedAliases,
1200312005
getBlockScopedVariableId,
@@ -12992,25 +12994,6 @@ module ts {
1299212994
}
1299312995
}
1299412996

12995-
function isIntegerLiteral(expression: Expression): boolean {
12996-
if (expression.kind === SyntaxKind.PrefixUnaryExpression) {
12997-
let unaryExpression = <PrefixUnaryExpression>expression;
12998-
if (unaryExpression.operator === SyntaxKind.PlusToken || unaryExpression.operator === SyntaxKind.MinusToken) {
12999-
expression = unaryExpression.operand;
13000-
}
13001-
}
13002-
if (expression.kind === SyntaxKind.NumericLiteral) {
13003-
// Allows for scientific notation since literalExpression.text was formed by
13004-
// coercing a number to a string. Sometimes this coercion can yield a string
13005-
// in scientific notation.
13006-
// We also don't need special logic for hex because a hex integer is converted
13007-
// to decimal when it is coerced.
13008-
return /^[0-9]+([eE]\+?[0-9]+)?$/.test((<LiteralExpression>expression).text);
13009-
}
13010-
13011-
return false;
13012-
}
13013-
1301412997
function checkGrammarEnumDeclaration(enumDecl: EnumDeclaration): boolean {
1301512998
let enumIsConst = (enumDecl.flags & NodeFlags.Const) !== 0;
1301612999

@@ -13019,26 +13002,18 @@ module ts {
1301913002
// skip checks below for const enums - they allow arbitrary initializers as long as they can be evaluated to constant expressions.
1302013003
// since all values are known in compile time - it is not necessary to check that constant enum section precedes computed enum members.
1302113004
if (!enumIsConst) {
13022-
let inConstantEnumMemberSection = true;
1302313005
let inAmbientContext = isInAmbientContext(enumDecl);
1302413006
for (let node of enumDecl.members) {
1302513007
// Do not use hasDynamicName here, because that returns false for well known symbols.
1302613008
// We want to perform checkComputedPropertyName for all computed properties, including
1302713009
// well known symbols.
1302813010
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
1302913011
hasError = grammarErrorOnNode(node.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
13030-
}
13031-
else if (inAmbientContext) {
13032-
if (node.initializer && !isIntegerLiteral(node.initializer)) {
13033-
hasError = grammarErrorOnNode(node.name, Diagnostics.Ambient_enum_elements_can_only_have_integer_literal_initializers) || hasError;
13012+
} else if (inAmbientContext) {
13013+
if (node.initializer && getEnumMemberValue(node) === undefined) {
13014+
hasError = grammarErrorOnNode(node.initializer, Diagnostics.Ambient_enum_initializer_must_be_a_constant_expression) || hasError;
1303413015
}
1303513016
}
13036-
else if (node.initializer) {
13037-
inConstantEnumMemberSection = isIntegerLiteral(node.initializer);
13038-
}
13039-
else if (!inConstantEnumMemberSection) {
13040-
hasError = grammarErrorOnNode(node.name, Diagnostics.Enum_member_must_have_initializer) || hasError;
13041-
}
1304213017
}
1304313018
}
1304413019

src/compiler/diagnosticInformationMap.generated.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ module ts {
4545
A_set_accessor_cannot_have_rest_parameter: { code: 1053, category: DiagnosticCategory.Error, key: "A 'set' accessor cannot have rest parameter." },
4646
A_get_accessor_cannot_have_parameters: { code: 1054, category: DiagnosticCategory.Error, key: "A 'get' accessor cannot have parameters." },
4747
Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher: { code: 1056, category: DiagnosticCategory.Error, key: "Accessors are only available when targeting ECMAScript 5 and higher." },
48-
Enum_member_must_have_initializer: { code: 1061, category: DiagnosticCategory.Error, key: "Enum member must have initializer." },
4948
An_export_assignment_cannot_be_used_in_a_namespace: { code: 1063, category: DiagnosticCategory.Error, key: "An export assignment cannot be used in a namespace." },
50-
Ambient_enum_elements_can_only_have_integer_literal_initializers: { code: 1066, category: DiagnosticCategory.Error, key: "Ambient enum elements can only have integer literal initializers." },
49+
Ambient_enum_initializer_must_be_a_constant_expression: { code: 1066, category: DiagnosticCategory.Error, key: "Ambient enum initializer must be a constant expression." },
5150
Unexpected_token_A_constructor_method_accessor_or_property_was_expected: { code: 1068, category: DiagnosticCategory.Error, key: "Unexpected token. A constructor, method, accessor, or property was expected." },
5251
A_declare_modifier_cannot_be_used_with_an_import_declaration: { code: 1079, category: DiagnosticCategory.Error, key: "A 'declare' modifier cannot be used with an import declaration." },
5352
Invalid_reference_directive_syntax: { code: 1084, category: DiagnosticCategory.Error, key: "Invalid 'reference' directive syntax." },
@@ -336,9 +335,9 @@ module ts {
336335
A_computed_property_name_of_the_form_0_must_be_of_type_symbol: { code: 2471, category: DiagnosticCategory.Error, key: "A computed property name of the form '{0}' must be of type 'symbol'." },
337336
Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_6_and_higher: { code: 2472, category: DiagnosticCategory.Error, key: "Spread operator in 'new' expressions is only available when targeting ECMAScript 6 and higher." },
338337
Enum_declarations_must_all_be_const_or_non_const: { code: 2473, category: DiagnosticCategory.Error, key: "Enum declarations must all be const or non-const." },
339-
In_const_enum_declarations_member_initializer_must_be_constant_expression: { code: 2474, category: DiagnosticCategory.Error, key: "In 'const' enum declarations member initializer must be constant expression." },
340-
const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment: { code: 2475, category: DiagnosticCategory.Error, key: "'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment." },
341-
A_const_enum_member_can_only_be_accessed_using_a_string_literal: { code: 2476, category: DiagnosticCategory.Error, key: "A const enum member can only be accessed using a string literal." },
338+
const_enum_initializer_must_be_a_constant_expression: { code: 2474, category: DiagnosticCategory.Error, key: "'const' enum initializer must be a constant expression." },
339+
const_enum_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment: { code: 2475, category: DiagnosticCategory.Error, key: "'const' enum can only be used in property or index access expressions or the right hand side of an import declaration or export assignment." },
340+
const_enum_member_can_only_be_accessed_using_a_string_literal: { code: 2476, category: DiagnosticCategory.Error, key: "'const' enum member can only be accessed using a string literal." },
342341
const_enum_member_initializer_was_evaluated_to_a_non_finite_value: { code: 2477, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to a non-finite value." },
343342
const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN: { code: 2478, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to disallowed value 'NaN'." },
344343
Property_0_does_not_exist_on_const_enum_1: { code: 2479, category: DiagnosticCategory.Error, key: "Property '{0}' does not exist on 'const' enum '{1}'." },

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,11 @@
167167
"category": "Error",
168168
"code": 1056
169169
},
170-
"Enum member must have initializer.": {
171-
"category": "Error",
172-
"code": 1061
173-
},
174170
"An export assignment cannot be used in a namespace.": {
175171
"category": "Error",
176172
"code": 1063
177173
},
178-
"Ambient enum elements can only have integer literal initializers.": {
174+
"Ambient enum initializer must be a constant expression.": {
179175
"category": "Error",
180176
"code": 1066
181177
},
@@ -1332,15 +1328,15 @@
13321328
"category": "Error",
13331329
"code": 2473
13341330
},
1335-
"In 'const' enum declarations member initializer must be constant expression.": {
1331+
"'const' enum initializer must be a constant expression.": {
13361332
"category": "Error",
13371333
"code": 2474
13381334
},
1339-
"'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment.": {
1335+
"'const' enum can only be used in property or index access expressions or the right hand side of an import declaration or export assignment.": {
13401336
"category": "Error",
13411337
"code": 2475
13421338
},
1343-
"A const enum member can only be accessed using a string literal.": {
1339+
"'const' enum member can only be accessed using a string literal.": {
13441340
"category": "Error",
13451341
"code": 2476
13461342
},

src/compiler/emitter.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4437,12 +4437,26 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
44374437
}
44384438
}
44394439

4440+
function isExpressionMemberOf(expr: Expression, parent: Node) {
4441+
if (expr) {
4442+
let memberType = resolver.getTypeOfExpression(expr);
4443+
return memberType.symbol === parent.symbol;
4444+
}
4445+
return false;
4446+
}
4447+
44404448
function emitEnumMember(node: EnumMember) {
4441-
let enumParent = <EnumDeclaration>node.parent;
4449+
let enumName = getGeneratedNameForNode(<EnumDeclaration>node.parent);
44424450
emitStart(node);
4443-
write(getGeneratedNameForNode(enumParent));
4451+
4452+
// If referencing member from same type/enum, emit a reference
4453+
if (isExpressionMemberOf(node.initializer, node.parent)) {
4454+
return emitEnumMemberReference(enumName, node, node.initializer)
4455+
}
4456+
4457+
write(enumName);
44444458
write("[");
4445-
write(getGeneratedNameForNode(enumParent));
4459+
write(enumName);
44464460
write("[");
44474461
emitExpressionForPropertyName(node.name);
44484462
write("] = ");
@@ -4453,6 +4467,27 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
44534467
write(";");
44544468
}
44554469

4470+
function emitEnumMemberReference(enumName: string, node: EnumMember, refNode: Expression) {
4471+
write(enumName);
4472+
write("[");
4473+
emitExpressionForPropertyName(node.name);
4474+
write("] = ");
4475+
emitEnumExpression(enumName, refNode);
4476+
emitEnd(node);
4477+
write(";");
4478+
}
4479+
4480+
function emitEnumExpression(enumName: string, node: Node) {
4481+
switch (node.kind) {
4482+
case SyntaxKind.PropertyAccessExpression:
4483+
return emitPropertyAccess(<PropertyAccessExpression>node);
4484+
case SyntaxKind.ElementAccessExpression:
4485+
return emitIndexedAccess(<ElementAccessExpression>node);
4486+
case SyntaxKind.Identifier:
4487+
return write(enumName + '.' + (<Identifier>node).text);
4488+
}
4489+
}
4490+
44564491
function writeEnumMemberDeclarationValue(member: EnumMember) {
44574492
let value = resolver.getConstantValue(member);
44584493
if (value !== undefined) {

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,6 +1278,7 @@ module ts {
12781278
isEntityNameVisible(entityName: EntityName | Expression, enclosingDeclaration: Node): SymbolVisibilityResult;
12791279
// Returns the constant value this property access resolves to, or 'undefined' for a non-constant
12801280
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number;
1281+
getTypeOfExpression(expr: Expression): Type;
12811282
resolvesToSomeValue(location: Node, name: string): boolean;
12821283
getBlockScopedVariableId(node: Identifier): number;
12831284
getReferencedValueDeclaration(reference: Identifier): Declaration;
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: Ambient enum initializer must be a 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: Ambient enum initializer must be a constant expression.
1714
}

0 commit comments

Comments
 (0)