Skip to content

Commit fa3c19d

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 681954b commit fa3c19d

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,
@@ -6388,7 +6389,7 @@ module ts {
63886389
let isConstEnum = isConstEnumObjectType(objectType);
63896390
if (isConstEnum &&
63906391
(!node.argumentExpression || node.argumentExpression.kind !== SyntaxKind.StringLiteral)) {
6391-
error(node.argumentExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal);
6392+
error(node.argumentExpression, Diagnostics.const_enum_member_can_only_be_accessed_using_a_string_literal);
63926393
return unknownType;
63936394
}
63946395

@@ -8067,7 +8068,7 @@ module ts {
80678068
((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(<Identifier>node));
80688069

80698070
if (!ok) {
8070-
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);
8071+
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);
80718072
}
80728073
}
80738074
return type;
@@ -10255,6 +10256,7 @@ module ts {
1025510256
let enumSymbol = getSymbolOfNode(node);
1025610257
let enumType = getDeclaredTypeOfSymbol(enumSymbol);
1025710258
let autoValue = 0;
10259+
let initValue: number;
1025810260
let ambient = isInAmbientContext(node);
1025910261
let enumIsConst = isConst(node);
1026010262

@@ -10264,10 +10266,10 @@ module ts {
1026410266
}
1026510267
let initializer = member.initializer;
1026610268
if (initializer) {
10267-
autoValue = getConstantValueForEnumMemberInitializer(initializer);
10268-
if (autoValue === undefined) {
10269+
initValue = getConstantValueForEnumMemberInitializer(initializer);
10270+
if (initValue === undefined) {
1026910271
if (enumIsConst) {
10270-
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
10272+
error(initializer, Diagnostics.const_enum_initializer_must_be_a_constant_expression);
1027110273
}
1027210274
else if (!ambient) {
1027310275
// Only here do we need to check that the initializer is assignable to the enum type.
@@ -10276,19 +10278,18 @@ module ts {
1027610278
// a syntax error if it is not a constant.
1027710279
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*headMessage*/ undefined);
1027810280
}
10281+
return;
1027910282
}
1028010283
else if (enumIsConst) {
10281-
if (isNaN(autoValue)) {
10282-
error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN);
10284+
if (isNaN(initValue)) {
10285+
return error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN);
1028310286
}
10284-
else if (!isFinite(autoValue)) {
10285-
error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
10287+
else if (!isFinite(initValue)) {
10288+
return error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
1028610289
}
10290+
1028710291
}
10288-
10289-
}
10290-
else if (ambient && !enumIsConst) {
10291-
autoValue = undefined;
10292+
autoValue = initValue;
1029210293
}
1029310294

1029410295
if (autoValue !== undefined) {
@@ -10423,6 +10424,8 @@ module ts {
1042310424
return;
1042410425
}
1042510426

10427+
computeEnumMemberValues(node);
10428+
1042610429
// Grammar checking
1042710430
checkGrammarDeclarationNameInStrictMode(node) || checkGrammarDecorators(node) || checkGrammarModifiers(node) || checkGrammarEnumDeclaration(node);
1042810431

@@ -10431,8 +10434,6 @@ module ts {
1043110434
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
1043210435
checkExportsOnMergedDeclarations(node);
1043310436

10434-
computeEnumMemberValues(node);
10435-
1043610437
let enumIsConst = isConst(node);
1043710438
if (compilerOptions.separateCompilation && enumIsConst && isInAmbientContext(node)) {
1043810439
error(node.name, Diagnostics.Ambient_const_enums_are_not_allowed_when_the_separateCompilation_flag_is_provided);
@@ -11984,6 +11985,7 @@ module ts {
1198411985
isSymbolAccessible,
1198511986
isEntityNameVisible,
1198611987
getConstantValue,
11988+
getTypeOfExpression,
1198711989
resolvesToSomeValue,
1198811990
collectLinkedAliases,
1198911991
getBlockScopedVariableId,
@@ -12978,25 +12980,6 @@ module ts {
1297812980
}
1297912981
}
1298012982

12981-
function isIntegerLiteral(expression: Expression): boolean {
12982-
if (expression.kind === SyntaxKind.PrefixUnaryExpression) {
12983-
let unaryExpression = <PrefixUnaryExpression>expression;
12984-
if (unaryExpression.operator === SyntaxKind.PlusToken || unaryExpression.operator === SyntaxKind.MinusToken) {
12985-
expression = unaryExpression.operand;
12986-
}
12987-
}
12988-
if (expression.kind === SyntaxKind.NumericLiteral) {
12989-
// Allows for scientific notation since literalExpression.text was formed by
12990-
// coercing a number to a string. Sometimes this coercion can yield a string
12991-
// in scientific notation.
12992-
// We also don't need special logic for hex because a hex integer is converted
12993-
// to decimal when it is coerced.
12994-
return /^[0-9]+([eE]\+?[0-9]+)?$/.test((<LiteralExpression>expression).text);
12995-
}
12996-
12997-
return false;
12998-
}
12999-
1300012983
function checkGrammarEnumDeclaration(enumDecl: EnumDeclaration): boolean {
1300112984
let enumIsConst = (enumDecl.flags & NodeFlags.Const) !== 0;
1300212985

@@ -13005,26 +12988,18 @@ module ts {
1300512988
// skip checks below for const enums - they allow arbitrary initializers as long as they can be evaluated to constant expressions.
1300612989
// since all values are known in compile time - it is not necessary to check that constant enum section precedes computed enum members.
1300712990
if (!enumIsConst) {
13008-
let inConstantEnumMemberSection = true;
1300912991
let inAmbientContext = isInAmbientContext(enumDecl);
1301012992
for (let node of enumDecl.members) {
1301112993
// Do not use hasDynamicName here, because that returns false for well known symbols.
1301212994
// We want to perform checkComputedPropertyName for all computed properties, including
1301312995
// well known symbols.
1301412996
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
1301512997
hasError = grammarErrorOnNode(node.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
13016-
}
13017-
else if (inAmbientContext) {
13018-
if (node.initializer && !isIntegerLiteral(node.initializer)) {
13019-
hasError = grammarErrorOnNode(node.name, Diagnostics.Ambient_enum_elements_can_only_have_integer_literal_initializers) || hasError;
12998+
} else if (inAmbientContext) {
12999+
if (node.initializer && getEnumMemberValue(node) === undefined) {
13000+
hasError = grammarErrorOnNode(node.initializer, Diagnostics.Ambient_enum_initializer_must_be_a_constant_expression) || hasError;
1302013001
}
1302113002
}
13022-
else if (node.initializer) {
13023-
inConstantEnumMemberSection = isIntegerLiteral(node.initializer);
13024-
}
13025-
else if (!inConstantEnumMemberSection) {
13026-
hasError = grammarErrorOnNode(node.name, Diagnostics.Enum_member_must_have_initializer) || hasError;
13027-
}
1302813003
}
1302913004
}
1303013005

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
@@ -4418,12 +4418,26 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
44184418
}
44194419
}
44204420

4421+
function isExpressionMemberOf(expr: Expression, parent: Node) {
4422+
if (expr) {
4423+
let memberType = resolver.getTypeOfExpression(expr);
4424+
return memberType.symbol === parent.symbol;
4425+
}
4426+
return false;
4427+
}
4428+
44214429
function emitEnumMember(node: EnumMember) {
4422-
let enumParent = <EnumDeclaration>node.parent;
4430+
let enumName = getGeneratedNameForNode(<EnumDeclaration>node.parent);
44234431
emitStart(node);
4424-
write(getGeneratedNameForNode(enumParent));
4432+
4433+
// If referencing member from same type/enum, emit a reference
4434+
if (isExpressionMemberOf(node.initializer, node.parent)) {
4435+
return emitEnumMemberReference(enumName, node, node.initializer)
4436+
}
4437+
4438+
write(enumName);
44254439
write("[");
4426-
write(getGeneratedNameForNode(enumParent));
4440+
write(enumName);
44274441
write("[");
44284442
emitExpressionForPropertyName(node.name);
44294443
write("] = ");
@@ -4434,6 +4448,27 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
44344448
write(";");
44354449
}
44364450

4451+
function emitEnumMemberReference(enumName: string, node: EnumMember, refNode: Expression) {
4452+
write(enumName);
4453+
write("[");
4454+
emitExpressionForPropertyName(node.name);
4455+
write("] = ");
4456+
emitEnumExpression(enumName, refNode);
4457+
emitEnd(node);
4458+
write(";");
4459+
}
4460+
4461+
function emitEnumExpression(enumName: string, node: Node) {
4462+
switch (node.kind) {
4463+
case SyntaxKind.PropertyAccessExpression:
4464+
return emitPropertyAccess(<PropertyAccessExpression>node);
4465+
case SyntaxKind.ElementAccessExpression:
4466+
return emitIndexedAccess(<ElementAccessExpression>node);
4467+
case SyntaxKind.Identifier:
4468+
return write(enumName + '.' + (<Identifier>node).text);
4469+
}
4470+
}
4471+
44374472
function writeEnumMemberDeclarationValue(member: EnumMember) {
44384473
let value = resolver.getConstantValue(member);
44394474
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)