diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 18e57303c12..a3e65b4eb65 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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(member.name)) { - error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums); - } - else { - const text = getTextOfPropertyName(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(member.name)) { + error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums); + } + else { + const text = getTextOfPropertyName(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((e).operand); - if (value === undefined) { - return undefined; - } - switch ((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((expr).operand); + if (typeof value === "number") { + switch ((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((e).left); - if (left === undefined) { - return undefined; - } - const right = evalConstant((e).right); - if (right === undefined) { - return undefined; - } - switch ((e).operatorToken.kind) { + } + break; + case SyntaxKind.BinaryExpression: + const left = evaluate((expr).left); + const right = evaluate((expr).right); + if (typeof left === "number" && typeof right === "number") { + switch ((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(e); - return +(e).text; - case SyntaxKind.ParenthesizedExpression: - return evalConstant((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 = (e).text; + } + break; + case SyntaxKind.NumericLiteral: + checkGrammarNumericLiteral(expr); + return +(expr).text; + case SyntaxKind.ParenthesizedExpression: + return evaluate((expr).expression); + case SyntaxKind.Identifier: + return evaluateEnumMember(expr, getSymbolOfNode(member.parent), (expr).text); + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: + if (isConstantMemberAccess(expr)) { + const type = getTypeOfExpression((expr).expression); + if (type.symbol && type.symbol.flags & SymbolFlags.Enum) { + const name = expr.kind === SyntaxKind.PropertyAccessExpression ? + (expr).name.text : + ((expr).argumentExpression).text; + return evaluateEnumMember(expr, type.symbol, name); } - else { - let expression: Expression; - if (e.kind === SyntaxKind.ElementAccessExpression) { - if ((e).argumentExpression === undefined || - (e).argumentExpression.kind !== SyntaxKind.StringLiteral) { - return undefined; - } - expression = (e).expression; - propertyName = ((e).argumentExpression).text; - } - else { - expression = (e).expression; - propertyName = (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 = (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 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((node).expression) || + node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((node).expression) && + (node).argumentExpression.kind === SyntaxKind.StringLiteral; + } + function checkEnumDeclaration(node: EnumDeclaration) { if (!produceDiagnostics) { return;