Skip to content

Enum improvements #3103

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 3 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
75 changes: 26 additions & 49 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ module ts {
getFullyQualifiedName,
getResolvedSignature,
getConstantValue,
getTypeOfExpression,
isValidPropertyAccess,
getSignatureFromDeclaration,
isImplementationOfOverload,
Expand Down Expand Up @@ -6388,7 +6389,7 @@ module ts {
let isConstEnum = isConstEnumObjectType(objectType);
if (isConstEnum &&
(!node.argumentExpression || node.argumentExpression.kind !== SyntaxKind.StringLiteral)) {
error(node.argumentExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal);
error(node.argumentExpression, Diagnostics.const_enum_member_can_only_be_accessed_using_a_string_literal);
return unknownType;
}

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

if (!ok) {
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);
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);
}
}
return type;
Expand Down Expand Up @@ -10250,45 +10251,47 @@ module ts {

function computeEnumMemberValues(node: EnumDeclaration) {
let nodeLinks = getNodeLinks(node);
let errorReported: boolean

if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) {
let enumSymbol = getSymbolOfNode(node);
let enumType = getDeclaredTypeOfSymbol(enumSymbol);
let autoValue = 0;
let initValue: number;
let ambient = isInAmbientContext(node);
let enumIsConst = isConst(node);

forEach(node.members, member => {
errorReported = false;
if (member.name.kind !== SyntaxKind.ComputedPropertyName && isNumericLiteralName((<Identifier>member.name).text)) {
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
}
let initializer = member.initializer;
if (initializer) {
autoValue = getConstantValueForEnumMemberInitializer(initializer);
if (autoValue === undefined) {
if (enumIsConst) {
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
initValue = getConstantValueForEnumMemberInitializer(initializer);
if (initValue === undefined) {
if (enumIsConst || ambient) {
!errorReported && error(initializer, Diagnostics._0_enum_initializer_must_be_a_constant_expression, enumIsConst ? "'const'" : "Ambient");
}
else if (!ambient) {
else {
// Only here do we need to check that the initializer is assignable to the enum type.
// If it is a constant value (not undefined), it is syntactically constrained to be a number.
// Also, we do not need to check this for ambients because there is already
// a syntax error if it is not a constant.
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*headMessage*/ undefined);
}
return;
}
else if (enumIsConst) {
if (isNaN(autoValue)) {
error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN);
if (isNaN(initValue)) {
return error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN);
}
else if (!isFinite(autoValue)) {
error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
else if (!isFinite(initValue)) {
return error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
}

}

}
else if (ambient && !enumIsConst) {
autoValue = undefined;
autoValue = initValue;
}

if (autoValue !== undefined) {
Expand Down Expand Up @@ -10345,7 +10348,7 @@ module ts {
case SyntaxKind.Identifier:
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.PropertyAccessExpression:
let member = initializer.parent;
let member = <EnumMember>initializer.parent;
let currentType = getTypeOfSymbol(getSymbolOfNode(member.parent));
let enumType: Type;
let propertyName: string;
Expand Down Expand Up @@ -10404,11 +10407,15 @@ module ts {
let propertyDecl = property.valueDeclaration;
// self references are illegal
if (member === propertyDecl) {
errorReported = true;
error(initializer, Diagnostics.Enum_member_0_cannot_reference_itself, propertyName);
return undefined;
}

// illegal case: forward reference
if (!isDefinedBefore(propertyDecl, member)) {
errorReported = true;
error(initializer, Diagnostics.Enum_member_0_must_be_declared_after_1, getTextOfNode(member.name), propertyName);
return undefined;
}

Expand All @@ -10423,6 +10430,8 @@ module ts {
return;
}

computeEnumMemberValues(node);

// Grammar checking
checkGrammarDeclarationNameInStrictMode(node) || checkGrammarDecorators(node) || checkGrammarModifiers(node) || checkGrammarEnumDeclaration(node);

Expand All @@ -10431,8 +10440,6 @@ module ts {
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
checkExportsOnMergedDeclarations(node);

computeEnumMemberValues(node);

let enumIsConst = isConst(node);
if (compilerOptions.separateCompilation && enumIsConst && isInAmbientContext(node)) {
error(node.name, Diagnostics.Ambient_const_enums_are_not_allowed_when_the_separateCompilation_flag_is_provided);
Expand Down Expand Up @@ -11984,6 +11991,7 @@ module ts {
isSymbolAccessible,
isEntityNameVisible,
getConstantValue,
getTypeOfExpression,
resolvesToSomeValue,
collectLinkedAliases,
getBlockScopedVariableId,
Expand Down Expand Up @@ -12978,25 +12986,6 @@ module ts {
}
}

function isIntegerLiteral(expression: Expression): boolean {
if (expression.kind === SyntaxKind.PrefixUnaryExpression) {
let unaryExpression = <PrefixUnaryExpression>expression;
if (unaryExpression.operator === SyntaxKind.PlusToken || unaryExpression.operator === SyntaxKind.MinusToken) {
expression = unaryExpression.operand;
}
}
if (expression.kind === SyntaxKind.NumericLiteral) {
// Allows for scientific notation since literalExpression.text was formed by
// coercing a number to a string. Sometimes this coercion can yield a string
// in scientific notation.
// We also don't need special logic for hex because a hex integer is converted
// to decimal when it is coerced.
return /^[0-9]+([eE]\+?[0-9]+)?$/.test((<LiteralExpression>expression).text);
}

return false;
}

function checkGrammarEnumDeclaration(enumDecl: EnumDeclaration): boolean {
let enumIsConst = (enumDecl.flags & NodeFlags.Const) !== 0;

Expand All @@ -13005,7 +12994,6 @@ module ts {
// skip checks below for const enums - they allow arbitrary initializers as long as they can be evaluated to constant expressions.
// since all values are known in compile time - it is not necessary to check that constant enum section precedes computed enum members.
if (!enumIsConst) {
let inConstantEnumMemberSection = true;
let inAmbientContext = isInAmbientContext(enumDecl);
for (let node of enumDecl.members) {
// Do not use hasDynamicName here, because that returns false for well known symbols.
Expand All @@ -13014,17 +13002,6 @@ module ts {
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
hasError = grammarErrorOnNode(node.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
}
else if (inAmbientContext) {
if (node.initializer && !isIntegerLiteral(node.initializer)) {
hasError = grammarErrorOnNode(node.name, Diagnostics.Ambient_enum_elements_can_only_have_integer_literal_initializers) || hasError;
}
}
else if (node.initializer) {
inConstantEnumMemberSection = isIntegerLiteral(node.initializer);
}
else if (!inConstantEnumMemberSection) {
hasError = grammarErrorOnNode(node.name, Diagnostics.Enum_member_must_have_initializer) || hasError;
}
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ module ts {
A_set_accessor_cannot_have_rest_parameter: { code: 1053, category: DiagnosticCategory.Error, key: "A 'set' accessor cannot have rest parameter." },
A_get_accessor_cannot_have_parameters: { code: 1054, category: DiagnosticCategory.Error, key: "A 'get' accessor cannot have parameters." },
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." },
Enum_member_must_have_initializer: { code: 1061, category: DiagnosticCategory.Error, key: "Enum member must have initializer." },
An_export_assignment_cannot_be_used_in_a_namespace: { code: 1063, category: DiagnosticCategory.Error, key: "An export assignment cannot be used in a namespace." },
Ambient_enum_elements_can_only_have_integer_literal_initializers: { code: 1066, category: DiagnosticCategory.Error, key: "Ambient enum elements can only have integer literal initializers." },
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." },
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." },
Invalid_reference_directive_syntax: { code: 1084, category: DiagnosticCategory.Error, key: "Invalid 'reference' directive syntax." },
Expand Down Expand Up @@ -336,14 +334,15 @@ module ts {
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'." },
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." },
Enum_declarations_must_all_be_const_or_non_const: { code: 2473, category: DiagnosticCategory.Error, key: "Enum declarations must all be const or non-const." },
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." },
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." },
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." },
_0_enum_initializer_must_be_a_constant_expression: { code: 2474, category: DiagnosticCategory.Error, key: "{0} enum initializer must be a constant expression." },
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." },
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." },
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." },
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'." },
Property_0_does_not_exist_on_const_enum_1: { code: 2479, category: DiagnosticCategory.Error, key: "Property '{0}' does not exist on 'const' enum '{1}'." },
let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations: { code: 2480, category: DiagnosticCategory.Error, key: "'let' is not allowed to be used as a name in 'let' or 'const' declarations." },
Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1: { code: 2481, category: DiagnosticCategory.Error, key: "Cannot initialize outer scoped variable '{0}' in the same scope as block scoped declaration '{1}'." },
Enum_member_0_must_be_declared_after_1: { code: 2482, category: DiagnosticCategory.Error, key: "Enum member '{0}' must be declared after '{1}'." },
The_left_hand_side_of_a_for_of_statement_cannot_use_a_type_annotation: { code: 2483, category: DiagnosticCategory.Error, key: "The left-hand side of a 'for...of' statement cannot use a type annotation." },
Export_declaration_conflicts_with_exported_declaration_of_0: { code: 2484, category: DiagnosticCategory.Error, key: "Export declaration conflicts with exported declaration of '{0}'" },
The_left_hand_side_of_a_for_of_statement_cannot_be_a_previously_defined_constant: { code: 2485, category: DiagnosticCategory.Error, key: "The left-hand side of a 'for...of' statement cannot be a previously defined constant." },
Expand Down Expand Up @@ -540,6 +539,7 @@ module ts {
enum_declarations_can_only_be_used_in_a_ts_file: { code: 8015, category: DiagnosticCategory.Error, key: "'enum declarations' can only be used in a .ts file." },
type_assertion_expressions_can_only_be_used_in_a_ts_file: { code: 8016, category: DiagnosticCategory.Error, key: "'type assertion expressions' can only be used in a .ts file." },
decorators_can_only_be_used_in_a_ts_file: { code: 8017, category: DiagnosticCategory.Error, key: "'decorators' can only be used in a .ts file." },
Enum_member_0_cannot_reference_itself: { code: 8016, category: DiagnosticCategory.Error, key: "Enum member '{0}' cannot reference itself." },
yield_expressions_are_not_currently_supported: { code: 9000, category: DiagnosticCategory.Error, key: "'yield' expressions are not currently supported." },
Generators_are_not_currently_supported: { code: 9001, category: DiagnosticCategory.Error, key: "Generators are not currently supported." },
Only_identifiers_Slashqualified_names_with_optional_type_arguments_are_currently_supported_in_a_class_extends_clauses: { code: 9002, category: DiagnosticCategory.Error, key: "Only identifiers/qualified-names with optional type arguments are currently supported in a class 'extends' clauses." },
Expand Down
23 changes: 11 additions & 12 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,18 +167,10 @@
"category": "Error",
"code": 1056
},
"Enum member must have initializer.": {
"category": "Error",
"code": 1061
},
"An export assignment cannot be used in a namespace.": {
"category": "Error",
"code": 1063
},
"Ambient enum elements can only have integer literal initializers.": {
"category": "Error",
"code": 1066
},
"Unexpected token. A constructor, method, accessor, or property was expected.": {
"category": "Error",
"code": 1068
Expand Down Expand Up @@ -1332,15 +1324,15 @@
"category": "Error",
"code": 2473
},
"In 'const' enum declarations member initializer must be constant expression.": {
"{0} enum initializer must be a constant expression.": {
"category": "Error",
"code": 2474
},
"'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment.": {
"'const' enum can only be used in property or index access expressions or the right hand side of an import declaration or export assignment.": {
"category": "Error",
"code": 2475
},
"A const enum member can only be accessed using a string literal.": {
"'const' enum member can only be accessed using a string literal.": {
"category": "Error",
"code": 2476
},
Expand All @@ -1364,6 +1356,10 @@
"category": "Error",
"code": 2481
},
"Enum member '{0}' must be declared after '{1}'.": {
"category": "Error",
"code": 2482
},
"The left-hand side of a 'for...of' statement cannot use a type annotation.": {
"category": "Error",
"code": 2483
Expand Down Expand Up @@ -2152,7 +2148,10 @@
"category": "Error",
"code": 8017
},

"Enum member '{0}' cannot reference itself.": {
"category": "Error",
"code": 8016
},
"'yield' expressions are not currently supported.": {
"category": "Error",
"code": 9000
Expand Down
Loading