Clean up computeEnumMemberValues function
This commit is contained in:
parent
da5fd936e6
commit
8a1456ff75
1 changed files with 112 additions and 169 deletions
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue