Clean up computeEnumMemberValues function

This commit is contained in:
Anders Hejlsberg 2017-04-25 14:55:31 -07:00
parent da5fd936e6
commit 8a1456ff75

View file

@ -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);
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++;
}
}
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);
}
}
let autoValue = 0;
for (const member of node.members) {
const value = computeMemberValue(member, autoValue);
getNodeLinks(member).enumMemberValue = value;
autoValue = typeof value === "number" ? value + 1 : undefined;
}
}
}
return 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;
}
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 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 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;
@ -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;
}
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);
}
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;
}
}
break;
}
return undefined;
}
// 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;
}
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;