Skip to content

Commit 8a1456f

Browse files
committed
Clean up computeEnumMemberValues function
1 parent da5fd93 commit 8a1456f

File tree

1 file changed

+109
-166
lines changed

1 file changed

+109
-166
lines changed

src/compiler/checker.ts

Lines changed: 109 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -20669,107 +20669,86 @@ namespace ts {
2066920669

2067020670
function computeEnumMemberValues(node: EnumDeclaration) {
2067120671
const nodeLinks = getNodeLinks(node);
20672-
2067320672
if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) {
20674-
const enumSymbol = getSymbolOfNode(node);
20675-
const enumType = getDeclaredTypeOfSymbol(enumSymbol);
20676-
let autoValue = 0; // set to undefined when enum member is non-constant
20677-
const ambient = isInAmbientContext(node);
20678-
const enumIsConst = isConst(node);
20679-
20673+
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
20674+
let autoValue = 0;
2068020675
for (const member of node.members) {
20681-
if (isComputedNonLiteralName(<PropertyName>member.name)) {
20682-
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
20683-
}
20684-
else {
20685-
const text = getTextOfPropertyName(<PropertyName>member.name);
20686-
if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) {
20687-
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
20688-
}
20689-
}
20690-
20691-
const previousEnumMemberIsNonConstant = autoValue === undefined;
20692-
20693-
const initializer = member.initializer;
20694-
if (initializer) {
20695-
autoValue = computeConstantValueForEnumMemberInitializer(initializer, enumType, enumIsConst, ambient);
20696-
}
20697-
else if (ambient && !enumIsConst) {
20698-
// In ambient enum declarations that specify no const modifier, enum member declarations
20699-
// that omit a value are considered computed members (as opposed to having auto-incremented values assigned).
20700-
autoValue = undefined;
20701-
}
20702-
else if (previousEnumMemberIsNonConstant) {
20703-
// If the member declaration specifies no value, the member is considered a constant enum member.
20704-
// If the member is the first member in the enum declaration, it is assigned the value zero.
20705-
// Otherwise, it is assigned the value of the immediately preceding member plus one,
20706-
// and an error occurs if the immediately preceding member is not a constant enum member
20707-
error(member.name, Diagnostics.Enum_member_must_have_initializer);
20708-
}
20709-
20710-
if (autoValue !== undefined) {
20711-
getNodeLinks(member).enumMemberValue = autoValue;
20712-
autoValue++;
20713-
}
20676+
const value = computeMemberValue(member, autoValue);
20677+
getNodeLinks(member).enumMemberValue = value;
20678+
autoValue = typeof value === "number" ? value + 1 : undefined;
2071420679
}
20715-
20716-
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
2071720680
}
20681+
}
2071820682

20719-
function computeConstantValueForEnumMemberInitializer(initializer: Expression, enumType: Type, enumIsConst: boolean, ambient: boolean): number {
20720-
// Controls if error should be reported after evaluation of constant value is completed
20721-
// Can be false if another more precise error was already reported during evaluation.
20722-
let reportError = true;
20723-
const value = evalConstant(initializer);
20724-
20725-
if (reportError) {
20726-
if (value === undefined) {
20727-
if (enumIsConst) {
20728-
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
20729-
}
20730-
else if (ambient) {
20731-
error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression);
20732-
}
20733-
else {
20734-
// Only here do we need to check that the initializer is assignable to the enum type.
20735-
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*headMessage*/ undefined);
20736-
}
20737-
}
20738-
else if (enumIsConst) {
20739-
if (isNaN(value)) {
20740-
error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN);
20741-
}
20742-
else if (!isFinite(value)) {
20743-
error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
20744-
}
20745-
}
20683+
function computeMemberValue(member: EnumMember, autoValue: number) {
20684+
if (isComputedNonLiteralName(<PropertyName>member.name)) {
20685+
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
20686+
}
20687+
else {
20688+
const text = getTextOfPropertyName(<PropertyName>member.name);
20689+
if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) {
20690+
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
2074620691
}
20692+
}
20693+
if (member.initializer) {
20694+
return computeConstantValue(member);
20695+
}
20696+
// In ambient enum declarations that specify no const modifier, enum member declarations that omit
20697+
// a value are considered computed members (as opposed to having auto-incremented values).
20698+
if (isInAmbientContext(member.parent) && !isConst(member.parent)) {
20699+
return undefined;
20700+
}
20701+
// If the member declaration specifies no value, the member is considered a constant enum member.
20702+
// If the member is the first member in the enum declaration, it is assigned the value zero.
20703+
// Otherwise, it is assigned the value of the immediately preceding member plus one, and an error
20704+
// occurs if the immediately preceding member is not a constant enum member.
20705+
if (autoValue !== undefined) {
20706+
return autoValue;
20707+
}
20708+
error(member.name, Diagnostics.Enum_member_must_have_initializer);
20709+
return undefined;
20710+
}
2074720711

20748-
return value;
20712+
function computeConstantValue(member: EnumMember): number {
20713+
const isConstEnum = isConst(member.parent);
20714+
const initializer = member.initializer;
20715+
const value = evaluate(member.initializer);
20716+
if (value !== undefined) {
20717+
if (isConstEnum && !isFinite(value)) {
20718+
error(initializer, isNaN(value) ?
20719+
Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN :
20720+
Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
20721+
}
20722+
}
20723+
else if (isConstEnum) {
20724+
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
20725+
}
20726+
else if (isInAmbientContext(member.parent)) {
20727+
error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression);
20728+
}
20729+
else {
20730+
// Only here do we need to check that the initializer is assignable to the enum type.
20731+
checkTypeAssignableTo(checkExpression(initializer), getDeclaredTypeOfSymbol(getSymbolOfNode(member.parent)), initializer, /*headMessage*/ undefined);
20732+
}
20733+
return value;
2074920734

20750-
function evalConstant(e: Node): number {
20751-
switch (e.kind) {
20752-
case SyntaxKind.PrefixUnaryExpression:
20753-
const value = evalConstant((<PrefixUnaryExpression>e).operand);
20754-
if (value === undefined) {
20755-
return undefined;
20756-
}
20757-
switch ((<PrefixUnaryExpression>e).operator) {
20735+
function evaluate(expr: Expression): number {
20736+
switch (expr.kind) {
20737+
case SyntaxKind.PrefixUnaryExpression:
20738+
const value = evaluate((<PrefixUnaryExpression>expr).operand);
20739+
if (typeof value === "number") {
20740+
switch ((<PrefixUnaryExpression>expr).operator) {
2075820741
case SyntaxKind.PlusToken: return value;
2075920742
case SyntaxKind.MinusToken: return -value;
2076020743
case SyntaxKind.TildeToken: return ~value;
2076120744
}
20762-
return undefined;
20763-
case SyntaxKind.BinaryExpression:
20764-
const left = evalConstant((<BinaryExpression>e).left);
20765-
if (left === undefined) {
20766-
return undefined;
20767-
}
20768-
const right = evalConstant((<BinaryExpression>e).right);
20769-
if (right === undefined) {
20770-
return undefined;
20771-
}
20772-
switch ((<BinaryExpression>e).operatorToken.kind) {
20745+
}
20746+
break;
20747+
case SyntaxKind.BinaryExpression:
20748+
const left = evaluate((<BinaryExpression>expr).left);
20749+
const right = evaluate((<BinaryExpression>expr).right);
20750+
if (typeof left === "number" && typeof right === "number") {
20751+
switch ((<BinaryExpression>expr).operatorToken.kind) {
2077320752
case SyntaxKind.BarToken: return left | right;
2077420753
case SyntaxKind.AmpersandToken: return left & right;
2077520754
case SyntaxKind.GreaterThanGreaterThanToken: return left >> right;
@@ -20782,90 +20761,54 @@ namespace ts {
2078220761
case SyntaxKind.MinusToken: return left - right;
2078320762
case SyntaxKind.PercentToken: return left % right;
2078420763
}
20785-
return undefined;
20786-
case SyntaxKind.NumericLiteral:
20787-
checkGrammarNumericLiteral(<NumericLiteral>e);
20788-
return +(<NumericLiteral>e).text;
20789-
case SyntaxKind.ParenthesizedExpression:
20790-
return evalConstant((<ParenthesizedExpression>e).expression);
20791-
case SyntaxKind.Identifier:
20792-
case SyntaxKind.ElementAccessExpression:
20793-
case SyntaxKind.PropertyAccessExpression:
20794-
const member = initializer.parent;
20795-
const currentType = getTypeOfSymbol(getSymbolOfNode(member.parent));
20796-
let enumType: Type;
20797-
let propertyName: string;
20798-
20799-
if (e.kind === SyntaxKind.Identifier) {
20800-
// unqualified names can refer to member that reside in different declaration of the enum so just doing name resolution won't work.
20801-
// instead pick current enum type and later try to fetch member from the type
20802-
enumType = currentType;
20803-
propertyName = (<Identifier>e).text;
20804-
}
20805-
else {
20806-
let expression: Expression;
20807-
if (e.kind === SyntaxKind.ElementAccessExpression) {
20808-
if ((<ElementAccessExpression>e).argumentExpression === undefined ||
20809-
(<ElementAccessExpression>e).argumentExpression.kind !== SyntaxKind.StringLiteral) {
20810-
return undefined;
20811-
}
20812-
expression = (<ElementAccessExpression>e).expression;
20813-
propertyName = (<LiteralExpression>(<ElementAccessExpression>e).argumentExpression).text;
20814-
}
20815-
else {
20816-
expression = (<PropertyAccessExpression>e).expression;
20817-
propertyName = (<PropertyAccessExpression>e).name.text;
20818-
}
20819-
20820-
// expression part in ElementAccess\PropertyAccess should be either identifier or dottedName
20821-
let current = expression;
20822-
while (current) {
20823-
if (current.kind === SyntaxKind.Identifier) {
20824-
break;
20825-
}
20826-
else if (current.kind === SyntaxKind.PropertyAccessExpression) {
20827-
current = (<ElementAccessExpression>current).expression;
20828-
}
20829-
else {
20830-
return undefined;
20831-
}
20832-
}
20833-
20834-
enumType = getTypeOfExpression(expression);
20835-
// allow references to constant members of other enums
20836-
if (!(enumType.symbol && (enumType.symbol.flags & SymbolFlags.Enum))) {
20837-
return undefined;
20838-
}
20839-
}
20840-
20841-
if (propertyName === undefined) {
20842-
return undefined;
20843-
}
20844-
20845-
const property = getPropertyOfObjectType(enumType, propertyName);
20846-
if (!property || !(property.flags & SymbolFlags.EnumMember)) {
20847-
return undefined;
20848-
}
20849-
20850-
const propertyDecl = property.valueDeclaration;
20851-
// self references are illegal
20852-
if (member === propertyDecl) {
20853-
return undefined;
20854-
}
20855-
20856-
// illegal case: forward reference
20857-
if (!isBlockScopedNameDeclaredBeforeUse(propertyDecl, member)) {
20858-
reportError = false;
20859-
error(e, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums);
20860-
return undefined;
20764+
}
20765+
break;
20766+
case SyntaxKind.NumericLiteral:
20767+
checkGrammarNumericLiteral(<NumericLiteral>expr);
20768+
return +(<NumericLiteral>expr).text;
20769+
case SyntaxKind.ParenthesizedExpression:
20770+
return evaluate((<ParenthesizedExpression>expr).expression);
20771+
case SyntaxKind.Identifier:
20772+
return evaluateEnumMember(expr, getSymbolOfNode(member.parent), (<Identifier>expr).text);
20773+
case SyntaxKind.ElementAccessExpression:
20774+
case SyntaxKind.PropertyAccessExpression:
20775+
if (isConstantMemberAccess(expr)) {
20776+
const type = getTypeOfExpression((<PropertyAccessExpression | ElementAccessExpression>expr).expression);
20777+
if (type.symbol && type.symbol.flags & SymbolFlags.Enum) {
20778+
const name = expr.kind === SyntaxKind.PropertyAccessExpression ?
20779+
(<PropertyAccessExpression>expr).name.text :
20780+
(<LiteralExpression>(<ElementAccessExpression>expr).argumentExpression).text;
20781+
return evaluateEnumMember(expr, type.symbol, name);
2086120782
}
20783+
}
20784+
break;
20785+
}
20786+
return undefined;
20787+
}
2086220788

20863-
return <number>getNodeLinks(propertyDecl).enumMemberValue;
20789+
function evaluateEnumMember(expr: Expression, enumSymbol: Symbol, name: string) {
20790+
const memberSymbol = enumSymbol.exports.get(name);
20791+
if (memberSymbol) {
20792+
const declaration = memberSymbol.valueDeclaration;
20793+
if (declaration !== member) {
20794+
if (isBlockScopedNameDeclaredBeforeUse(declaration, member)) {
20795+
return getNodeLinks(declaration).enumMemberValue;
20796+
}
20797+
error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums);
20798+
return 0;
2086420799
}
2086520800
}
20801+
return undefined;
2086620802
}
2086720803
}
2086820804

20805+
function isConstantMemberAccess(node: Expression): boolean {
20806+
return node.kind === SyntaxKind.Identifier ||
20807+
node.kind === SyntaxKind.PropertyAccessExpression && isConstantMemberAccess((<PropertyAccessExpression>node).expression) ||
20808+
node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((<ElementAccessExpression>node).expression) &&
20809+
(<ElementAccessExpression>node).argumentExpression.kind === SyntaxKind.StringLiteral;
20810+
}
20811+
2086920812
function checkEnumDeclaration(node: EnumDeclaration) {
2087020813
if (!produceDiagnostics) {
2087120814
return;

0 commit comments

Comments
 (0)