Skip to content

Commit

Permalink
Make ambiant & const enums report similar errors (allow any constant …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
jbondc committed May 10, 2015
1 parent 681954b commit fa3c19d
Show file tree
Hide file tree
Showing 42 changed files with 514 additions and 212 deletions.
65 changes: 20 additions & 45 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 @@ -10255,6 +10256,7 @@ module ts {
let enumSymbol = getSymbolOfNode(node);
let enumType = getDeclaredTypeOfSymbol(enumSymbol);
let autoValue = 0;
let initValue: number;
let ambient = isInAmbientContext(node);
let enumIsConst = isConst(node);

Expand All @@ -10264,10 +10266,10 @@ module ts {
}
let initializer = member.initializer;
if (initializer) {
autoValue = getConstantValueForEnumMemberInitializer(initializer);
if (autoValue === undefined) {
initValue = getConstantValueForEnumMemberInitializer(initializer);
if (initValue === undefined) {
if (enumIsConst) {
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
error(initializer, Diagnostics.const_enum_initializer_must_be_a_constant_expression);
}
else if (!ambient) {
// Only here do we need to check that the initializer is assignable to the enum type.
Expand All @@ -10276,19 +10278,18 @@ module ts {
// 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 @@ -10423,6 +10424,8 @@ module ts {
return;
}

computeEnumMemberValues(node);

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

Expand All @@ -10431,8 +10434,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 +11985,7 @@ module ts {
isSymbolAccessible,
isEntityNameVisible,
getConstantValue,
getTypeOfExpression,
resolvesToSomeValue,
collectLinkedAliases,
getBlockScopedVariableId,
Expand Down Expand Up @@ -12978,25 +12980,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,26 +12988,18 @@ 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.
// We want to perform checkComputedPropertyName for all computed properties, including
// well known symbols.
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 (inAmbientContext) {
if (node.initializer && getEnumMemberValue(node) === undefined) {
hasError = grammarErrorOnNode(node.initializer, Diagnostics.Ambient_enum_initializer_must_be_a_constant_expression) || 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
9 changes: 4 additions & 5 deletions src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,8 @@ 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." },
Ambient_enum_initializer_must_be_a_constant_expression: { code: 1066, category: DiagnosticCategory.Error, key: "Ambient enum initializer must be a constant expression." },
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,9 +335,9 @@ 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." },
const_enum_initializer_must_be_a_constant_expression: { code: 2474, category: DiagnosticCategory.Error, key: "'const' 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}'." },
Expand Down
12 changes: 4 additions & 8 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,11 @@
"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.": {
"Ambient enum initializer must be a constant expression.": {
"category": "Error",
"code": 1066
},
Expand Down Expand Up @@ -1332,15 +1328,15 @@
"category": "Error",
"code": 2473
},
"In 'const' enum declarations member initializer must be constant expression.": {
"'const' 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 Down
41 changes: 38 additions & 3 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4418,12 +4418,26 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
}
}

function isExpressionMemberOf(expr: Expression, parent: Node) {
if (expr) {
let memberType = resolver.getTypeOfExpression(expr);
return memberType.symbol === parent.symbol;
}
return false;
}

function emitEnumMember(node: EnumMember) {
let enumParent = <EnumDeclaration>node.parent;
let enumName = getGeneratedNameForNode(<EnumDeclaration>node.parent);
emitStart(node);
write(getGeneratedNameForNode(enumParent));

// If referencing member from same type/enum, emit a reference
if (isExpressionMemberOf(node.initializer, node.parent)) {
return emitEnumMemberReference(enumName, node, node.initializer)
}

write(enumName);
write("[");
write(getGeneratedNameForNode(enumParent));
write(enumName);
write("[");
emitExpressionForPropertyName(node.name);
write("] = ");
Expand All @@ -4434,6 +4448,27 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
write(";");
}

function emitEnumMemberReference(enumName: string, node: EnumMember, refNode: Expression) {
write(enumName);
write("[");
emitExpressionForPropertyName(node.name);
write("] = ");
emitEnumExpression(enumName, refNode);
emitEnd(node);
write(";");
}

function emitEnumExpression(enumName: string, node: Node) {
switch (node.kind) {
case SyntaxKind.PropertyAccessExpression:
return emitPropertyAccess(<PropertyAccessExpression>node);
case SyntaxKind.ElementAccessExpression:
return emitIndexedAccess(<ElementAccessExpression>node);
case SyntaxKind.Identifier:
return write(enumName + '.' + (<Identifier>node).text);
}
}

function writeEnumMemberDeclarationValue(member: EnumMember) {
let value = resolver.getConstantValue(member);
if (value !== undefined) {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1278,6 +1278,7 @@ module ts {
isEntityNameVisible(entityName: EntityName | Expression, enclosingDeclaration: Node): SymbolVisibilityResult;
// Returns the constant value this property access resolves to, or 'undefined' for a non-constant
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number;
getTypeOfExpression(expr: Expression): Type;
resolvesToSomeValue(location: Node, name: string): boolean;
getBlockScopedVariableId(node: Identifier): number;
getReferencedValueDeclaration(reference: Identifier): Declaration;
Expand Down
11 changes: 4 additions & 7 deletions tests/baselines/reference/ambientEnum1.errors.txt
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
tests/cases/compiler/ambientEnum1.ts(2,9): error TS1066: Ambient enum elements can only have integer literal initializers.
tests/cases/compiler/ambientEnum1.ts(7,9): error TS1066: Ambient enum elements can only have integer literal initializers.
tests/cases/compiler/ambientEnum1.ts(7,13): error TS1066: Ambient enum initializer must be a constant expression.


==== tests/cases/compiler/ambientEnum1.ts (2 errors) ====
==== tests/cases/compiler/ambientEnum1.ts (1 errors) ====
declare enum E1 {
y = 4.23
~
!!! error TS1066: Ambient enum elements can only have integer literal initializers.
}

// Ambient enum with computer member
declare enum E2 {
x = 'foo'.length
~
!!! error TS1066: Ambient enum elements can only have integer literal initializers.
~~~~~~~~~~~~
!!! error TS1066: Ambient enum initializer must be a constant expression.
}
Loading

0 comments on commit fa3c19d

Please sign in to comment.