Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
Next Next commit
Clean up computeEnumMemberValues function
  • Loading branch information
ahejlsberg committed Apr 25, 2017
commit 8a1456ff7585ed87dc00e023d6168993dd36ee45
275 changes: 109 additions & 166 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20669,107 +20669,86 @@ namespace ts {

function computeEnumMemberValues(node: EnumDeclaration) {
const nodeLinks = getNodeLinks(node);

if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) {
const enumSymbol = getSymbolOfNode(node);
const enumType = getDeclaredTypeOfSymbol(enumSymbol);
let autoValue = 0; // set to undefined when enum member is non-constant
const ambient = isInAmbientContext(node);
const enumIsConst = isConst(node);

nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
let autoValue = 0;
for (const member of node.members) {
if (isComputedNonLiteralName(<PropertyName>member.name)) {
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
}
else {
const text = getTextOfPropertyName(<PropertyName>member.name);
if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) {
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
}
}

const previousEnumMemberIsNonConstant = autoValue === undefined;

const initializer = member.initializer;
if (initializer) {
autoValue = computeConstantValueForEnumMemberInitializer(initializer, enumType, enumIsConst, ambient);
}
else if (ambient && !enumIsConst) {
// In ambient enum declarations that specify no const modifier, enum member declarations
// that omit a value are considered computed members (as opposed to having auto-incremented values assigned).
autoValue = undefined;
}
else if (previousEnumMemberIsNonConstant) {
// If the member declaration specifies no value, the member is considered a constant enum member.
// If the member is the first member in the enum declaration, it is assigned the value zero.
// Otherwise, it is assigned the value of the immediately preceding member plus one,
// and an error occurs if the immediately preceding member is not a constant enum member
error(member.name, Diagnostics.Enum_member_must_have_initializer);
}

if (autoValue !== undefined) {
getNodeLinks(member).enumMemberValue = autoValue;
autoValue++;
}
const value = computeMemberValue(member, autoValue);
getNodeLinks(member).enumMemberValue = value;
autoValue = typeof value === "number" ? value + 1 : undefined;
}

nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
}
}

function computeConstantValueForEnumMemberInitializer(initializer: Expression, enumType: Type, enumIsConst: boolean, ambient: boolean): number {
// Controls if error should be reported after evaluation of constant value is completed
// Can be false if another more precise error was already reported during evaluation.
let reportError = true;
const value = evalConstant(initializer);

if (reportError) {
if (value === undefined) {
if (enumIsConst) {
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
}
else if (ambient) {
error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression);
}
else {
// Only here do we need to check that the initializer is assignable to the enum type.
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*headMessage*/ undefined);
}
}
else if (enumIsConst) {
if (isNaN(value)) {
error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN);
}
else if (!isFinite(value)) {
error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
}
}
function computeMemberValue(member: EnumMember, autoValue: number) {
if (isComputedNonLiteralName(<PropertyName>member.name)) {
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
}
else {
const text = getTextOfPropertyName(<PropertyName>member.name);
if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) {
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
}
}
if (member.initializer) {
return computeConstantValue(member);
}
// In ambient enum declarations that specify no const modifier, enum member declarations that omit
// a value are considered computed members (as opposed to having auto-incremented values).
if (isInAmbientContext(member.parent) && !isConst(member.parent)) {
return undefined;
}
// If the member declaration specifies no value, the member is considered a constant enum member.
// If the member is the first member in the enum declaration, it is assigned the value zero.
// Otherwise, it is assigned the value of the immediately preceding member plus one, and an error
// occurs if the immediately preceding member is not a constant enum member.
if (autoValue !== undefined) {
return autoValue;
}
error(member.name, Diagnostics.Enum_member_must_have_initializer);
return undefined;
}

return value;
function computeConstantValue(member: EnumMember): number {
const isConstEnum = isConst(member.parent);
const initializer = member.initializer;
const value = evaluate(member.initializer);
if (value !== undefined) {
if (isConstEnum && !isFinite(value)) {
error(initializer, isNaN(value) ?
Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN :
Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
}
}
else if (isConstEnum) {
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
}
else if (isInAmbientContext(member.parent)) {
error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression);
}
else {
// Only here do we need to check that the initializer is assignable to the enum type.
checkTypeAssignableTo(checkExpression(initializer), getDeclaredTypeOfSymbol(getSymbolOfNode(member.parent)), initializer, /*headMessage*/ undefined);
}
return value;

function evalConstant(e: Node): number {
switch (e.kind) {
case SyntaxKind.PrefixUnaryExpression:
const value = evalConstant((<PrefixUnaryExpression>e).operand);
if (value === undefined) {
return undefined;
}
switch ((<PrefixUnaryExpression>e).operator) {
function evaluate(expr: Expression): number {
switch (expr.kind) {
case SyntaxKind.PrefixUnaryExpression:
const value = evaluate((<PrefixUnaryExpression>expr).operand);
if (typeof value === "number") {
switch ((<PrefixUnaryExpression>expr).operator) {
case SyntaxKind.PlusToken: return value;
case SyntaxKind.MinusToken: return -value;
case SyntaxKind.TildeToken: return ~value;
}
return undefined;
case SyntaxKind.BinaryExpression:
const left = evalConstant((<BinaryExpression>e).left);
if (left === undefined) {
return undefined;
}
const right = evalConstant((<BinaryExpression>e).right);
if (right === undefined) {
return undefined;
}
switch ((<BinaryExpression>e).operatorToken.kind) {
}
break;
case SyntaxKind.BinaryExpression:
const left = evaluate((<BinaryExpression>expr).left);
const right = evaluate((<BinaryExpression>expr).right);
if (typeof left === "number" && typeof right === "number") {
switch ((<BinaryExpression>expr).operatorToken.kind) {
case SyntaxKind.BarToken: return left | right;
case SyntaxKind.AmpersandToken: return left & right;
case SyntaxKind.GreaterThanGreaterThanToken: return left >> right;
Expand All @@ -20782,90 +20761,54 @@ namespace ts {
case SyntaxKind.MinusToken: return left - right;
case SyntaxKind.PercentToken: return left % right;
}
return undefined;
case SyntaxKind.NumericLiteral:
checkGrammarNumericLiteral(<NumericLiteral>e);
return +(<NumericLiteral>e).text;
case SyntaxKind.ParenthesizedExpression:
return evalConstant((<ParenthesizedExpression>e).expression);
case SyntaxKind.Identifier:
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.PropertyAccessExpression:
const member = initializer.parent;
const currentType = getTypeOfSymbol(getSymbolOfNode(member.parent));
let enumType: Type;
let propertyName: string;

if (e.kind === SyntaxKind.Identifier) {
// unqualified names can refer to member that reside in different declaration of the enum so just doing name resolution won't work.
// instead pick current enum type and later try to fetch member from the type
enumType = currentType;
propertyName = (<Identifier>e).text;
}
else {
let expression: Expression;
if (e.kind === SyntaxKind.ElementAccessExpression) {
if ((<ElementAccessExpression>e).argumentExpression === undefined ||
(<ElementAccessExpression>e).argumentExpression.kind !== SyntaxKind.StringLiteral) {
return undefined;
}
expression = (<ElementAccessExpression>e).expression;
propertyName = (<LiteralExpression>(<ElementAccessExpression>e).argumentExpression).text;
}
else {
expression = (<PropertyAccessExpression>e).expression;
propertyName = (<PropertyAccessExpression>e).name.text;
}

// expression part in ElementAccess\PropertyAccess should be either identifier or dottedName
let current = expression;
while (current) {
if (current.kind === SyntaxKind.Identifier) {
break;
}
else if (current.kind === SyntaxKind.PropertyAccessExpression) {
current = (<ElementAccessExpression>current).expression;
}
else {
return undefined;
}
}

enumType = getTypeOfExpression(expression);
// allow references to constant members of other enums
if (!(enumType.symbol && (enumType.symbol.flags & SymbolFlags.Enum))) {
return undefined;
}
}

if (propertyName === undefined) {
return undefined;
}

const property = getPropertyOfObjectType(enumType, propertyName);
if (!property || !(property.flags & SymbolFlags.EnumMember)) {
return undefined;
}

const propertyDecl = property.valueDeclaration;
// self references are illegal
if (member === propertyDecl) {
return undefined;
}

// illegal case: forward reference
if (!isBlockScopedNameDeclaredBeforeUse(propertyDecl, member)) {
reportError = false;
error(e, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums);
return undefined;
}
break;
case SyntaxKind.NumericLiteral:
checkGrammarNumericLiteral(<NumericLiteral>expr);
return +(<NumericLiteral>expr).text;
case SyntaxKind.ParenthesizedExpression:
return evaluate((<ParenthesizedExpression>expr).expression);
case SyntaxKind.Identifier:
return evaluateEnumMember(expr, getSymbolOfNode(member.parent), (<Identifier>expr).text);
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.PropertyAccessExpression:
if (isConstantMemberAccess(expr)) {
const type = getTypeOfExpression((<PropertyAccessExpression | ElementAccessExpression>expr).expression);
if (type.symbol && type.symbol.flags & SymbolFlags.Enum) {
const name = expr.kind === SyntaxKind.PropertyAccessExpression ?
(<PropertyAccessExpression>expr).name.text :
(<LiteralExpression>(<ElementAccessExpression>expr).argumentExpression).text;
return evaluateEnumMember(expr, type.symbol, name);
}
}
break;
}
return undefined;
}

return <number>getNodeLinks(propertyDecl).enumMemberValue;
function evaluateEnumMember(expr: Expression, enumSymbol: Symbol, name: string) {
const memberSymbol = enumSymbol.exports.get(name);
if (memberSymbol) {
const declaration = memberSymbol.valueDeclaration;
if (declaration !== member) {
if (isBlockScopedNameDeclaredBeforeUse(declaration, member)) {
return getNodeLinks(declaration).enumMemberValue;
}
error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums);
return 0;
}
}
return undefined;
}
}

function isConstantMemberAccess(node: Expression): boolean {
return node.kind === SyntaxKind.Identifier ||
node.kind === SyntaxKind.PropertyAccessExpression && isConstantMemberAccess((<PropertyAccessExpression>node).expression) ||
node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((<ElementAccessExpression>node).expression) &&
(<ElementAccessExpression>node).argumentExpression.kind === SyntaxKind.StringLiteral;
}

function checkEnumDeclaration(node: EnumDeclaration) {
if (!produceDiagnostics) {
return;
Expand Down