Skip to content

Commit 6ec5053

Browse files
committed
Fix enum classification and evaluation
1 parent 2c68ded commit 6ec5053

File tree

1 file changed

+86
-91
lines changed

1 file changed

+86
-91
lines changed

src/compiler/checker.ts

Lines changed: 86 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -10559,7 +10559,7 @@ namespace ts {
1055910559
for (const declaration of symbol.declarations) {
1056010560
if (declaration.kind === SyntaxKind.EnumDeclaration) {
1056110561
for (const member of (declaration as EnumDeclaration).members) {
10562-
if (member.initializer && isStringLiteralLike(member.initializer)) {
10562+
if (member.initializer && isStringConcatExpression(member.initializer)) {
1056310563
return links.enumKind = EnumKind.Literal;
1056410564
}
1056510565
if (!isLiteralEnumMember(member)) {
@@ -40547,8 +40547,8 @@ namespace ts {
4054740547
const enumKind = getEnumKind(getSymbolOfNode(member.parent));
4054840548
const isConstEnum = isEnumConst(member.parent);
4054940549
const initializer = member.initializer!;
40550-
const value = enumKind === EnumKind.Literal && !isLiteralEnumMember(member) ? undefined : evaluate(initializer);
40551-
if (value !== undefined) {
40550+
const value = enumKind === EnumKind.Literal && !isLiteralEnumMember(member) ? undefined : evaluate(initializer, member);
40551+
if (value !== undefined && (enumKind !== EnumKind.Numeric || typeof value === "number")) {
4055240552
if (isConstEnum && typeof value === "number" && !isFinite(value)) {
4055340553
error(initializer, isNaN(value) ?
4055440554
Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN :
@@ -40576,105 +40576,100 @@ namespace ts {
4057640576
}
4057740577
}
4057840578
return value;
40579+
}
4057940580

40580-
function evaluate(expr: Expression): string | number | undefined {
40581-
switch (expr.kind) {
40582-
case SyntaxKind.PrefixUnaryExpression:
40583-
const value = evaluate((expr as PrefixUnaryExpression).operand);
40584-
if (typeof value === "number") {
40585-
switch ((expr as PrefixUnaryExpression).operator) {
40586-
case SyntaxKind.PlusToken: return value;
40587-
case SyntaxKind.MinusToken: return -value;
40588-
case SyntaxKind.TildeToken: return ~value;
40589-
}
40590-
}
40591-
break;
40592-
case SyntaxKind.BinaryExpression:
40593-
const left = evaluate((expr as BinaryExpression).left);
40594-
const right = evaluate((expr as BinaryExpression).right);
40595-
if (typeof left === "number" && typeof right === "number") {
40596-
switch ((expr as BinaryExpression).operatorToken.kind) {
40597-
case SyntaxKind.BarToken: return left | right;
40598-
case SyntaxKind.AmpersandToken: return left & right;
40599-
case SyntaxKind.GreaterThanGreaterThanToken: return left >> right;
40600-
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right;
40601-
case SyntaxKind.LessThanLessThanToken: return left << right;
40602-
case SyntaxKind.CaretToken: return left ^ right;
40603-
case SyntaxKind.AsteriskToken: return left * right;
40604-
case SyntaxKind.SlashToken: return left / right;
40605-
case SyntaxKind.PlusToken: return left + right;
40606-
case SyntaxKind.MinusToken: return left - right;
40607-
case SyntaxKind.PercentToken: return left % right;
40608-
case SyntaxKind.AsteriskAsteriskToken: return left ** right;
40609-
}
40610-
}
40611-
else if (typeof left === "string" && typeof right === "string" && (expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken) {
40612-
return left + right;
40613-
}
40614-
break;
40615-
case SyntaxKind.StringLiteral:
40616-
case SyntaxKind.NoSubstitutionTemplateLiteral:
40617-
return (expr as StringLiteralLike).text;
40618-
case SyntaxKind.NumericLiteral:
40619-
checkGrammarNumericLiteral(expr as NumericLiteral);
40620-
return +(expr as NumericLiteral).text;
40621-
case SyntaxKind.ParenthesizedExpression:
40622-
return evaluate((expr as ParenthesizedExpression).expression);
40623-
case SyntaxKind.Identifier:
40624-
const identifier = expr as Identifier;
40625-
if (isInfinityOrNaNString(identifier.escapedText)) {
40626-
return +(identifier.escapedText);
40581+
function evaluate(expr: Expression, location: Declaration): string | number | undefined {
40582+
switch (expr.kind) {
40583+
case SyntaxKind.PrefixUnaryExpression:
40584+
const value = evaluate((expr as PrefixUnaryExpression).operand, location);
40585+
if (typeof value === "number") {
40586+
switch ((expr as PrefixUnaryExpression).operator) {
40587+
case SyntaxKind.PlusToken: return value;
40588+
case SyntaxKind.MinusToken: return -value;
40589+
case SyntaxKind.TildeToken: return ~value;
4062740590
}
40628-
return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), identifier.escapedText);
40629-
case SyntaxKind.ElementAccessExpression:
40630-
case SyntaxKind.PropertyAccessExpression:
40631-
if (isConstantMemberAccess(expr)) {
40632-
const type = getTypeOfExpression(expr.expression);
40633-
if (type.symbol && type.symbol.flags & SymbolFlags.Enum) {
40634-
let name: __String;
40635-
if (expr.kind === SyntaxKind.PropertyAccessExpression) {
40636-
name = expr.name.escapedText;
40637-
}
40638-
else {
40639-
name = escapeLeadingUnderscores(cast(expr.argumentExpression, isLiteralExpression).text);
40591+
}
40592+
break;
40593+
case SyntaxKind.BinaryExpression:
40594+
const left = evaluate((expr as BinaryExpression).left, location);
40595+
const right = evaluate((expr as BinaryExpression).right, location);
40596+
if (typeof left === "number" && typeof right === "number") {
40597+
switch ((expr as BinaryExpression).operatorToken.kind) {
40598+
case SyntaxKind.BarToken: return left | right;
40599+
case SyntaxKind.AmpersandToken: return left & right;
40600+
case SyntaxKind.GreaterThanGreaterThanToken: return left >> right;
40601+
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right;
40602+
case SyntaxKind.LessThanLessThanToken: return left << right;
40603+
case SyntaxKind.CaretToken: return left ^ right;
40604+
case SyntaxKind.AsteriskToken: return left * right;
40605+
case SyntaxKind.SlashToken: return left / right;
40606+
case SyntaxKind.PlusToken: return left + right;
40607+
case SyntaxKind.MinusToken: return left - right;
40608+
case SyntaxKind.PercentToken: return left % right;
40609+
case SyntaxKind.AsteriskAsteriskToken: return left ** right;
40610+
}
40611+
}
40612+
else if (typeof left === "string" && typeof right === "string" && (expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken) {
40613+
return left + right;
40614+
}
40615+
break;
40616+
case SyntaxKind.StringLiteral:
40617+
case SyntaxKind.NoSubstitutionTemplateLiteral:
40618+
return (expr as StringLiteralLike).text;
40619+
case SyntaxKind.NumericLiteral:
40620+
checkGrammarNumericLiteral(expr as NumericLiteral);
40621+
return +(expr as NumericLiteral).text;
40622+
case SyntaxKind.ParenthesizedExpression:
40623+
return evaluate((expr as ParenthesizedExpression).expression, location);
40624+
case SyntaxKind.Identifier:
40625+
if (isInfinityOrNaNString((expr as Identifier).escapedText)) {
40626+
return +((expr as Identifier).escapedText);
40627+
}
40628+
// falls through
40629+
case SyntaxKind.PropertyAccessExpression:
40630+
if (isEntityNameExpression(expr)) {
40631+
const symbol = resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true);
40632+
if (symbol) {
40633+
if (symbol.flags & SymbolFlags.EnumMember) {
40634+
return evaluateEnumMember(expr, symbol, location);
40635+
}
40636+
if (isConstVariable(symbol)) {
40637+
const declaration = symbol.valueDeclaration as VariableDeclaration | undefined;
40638+
if (declaration && !declaration.type && declaration.initializer && declaration !== location && isBlockScopedNameDeclaredBeforeUse(declaration, location)) {
40639+
return evaluate(declaration.initializer, declaration);
4064040640
}
40641-
return evaluateEnumMember(expr, type.symbol, name);
4064240641
}
4064340642
}
40644-
break;
40645-
}
40646-
return undefined;
40647-
}
40648-
40649-
function evaluateEnumMember(expr: Expression, enumSymbol: Symbol, name: __String) {
40650-
const memberSymbol = enumSymbol.exports!.get(name);
40651-
if (memberSymbol) {
40652-
const declaration = memberSymbol.valueDeclaration;
40653-
if (declaration !== member) {
40654-
if (declaration && isBlockScopedNameDeclaredBeforeUse(declaration, member) && isEnumDeclaration(declaration.parent)) {
40655-
return getEnumMemberValue(declaration as EnumMember);
40656-
}
40657-
error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums);
40658-
return 0;
4065940643
}
40660-
else {
40661-
error(expr, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(memberSymbol));
40644+
break;
40645+
case SyntaxKind.ElementAccessExpression:
40646+
const root = (expr as ElementAccessExpression).expression;
40647+
if (isEntityNameExpression(root) && isStringLiteralLike((expr as ElementAccessExpression).argumentExpression)) {
40648+
const rootSymbol = resolveEntityName(root, SymbolFlags.Value, /*ignoreErrors*/ true);
40649+
if (rootSymbol && rootSymbol.flags & SymbolFlags.Enum) {
40650+
const name = escapeLeadingUnderscores(((expr as ElementAccessExpression).argumentExpression as StringLiteralLike).text);
40651+
const member = rootSymbol.exports!.get(name);
40652+
if (member) {
40653+
return evaluateEnumMember(expr, member, location);
40654+
}
40655+
}
4066240656
}
40663-
}
40664-
return undefined;
40657+
break;
4066540658
}
40659+
return undefined;
4066640660
}
4066740661

40668-
function isConstantMemberAccess(node: Expression): node is AccessExpression {
40669-
const type = getTypeOfExpression(node);
40670-
if (type === errorType) {
40671-
return false;
40662+
function evaluateEnumMember(expr: Expression, symbol: Symbol, location: Declaration) {
40663+
const declaration = symbol.valueDeclaration;
40664+
if (!declaration || declaration === location) {
40665+
error(expr, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(symbol));
40666+
return undefined;
4067240667
}
40673-
40674-
return node.kind === SyntaxKind.Identifier ||
40675-
node.kind === SyntaxKind.PropertyAccessExpression && isConstantMemberAccess((node as PropertyAccessExpression).expression) ||
40676-
node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((node as ElementAccessExpression).expression) &&
40677-
isStringLiteralLike((node as ElementAccessExpression).argumentExpression);
40668+
if (!isBlockScopedNameDeclaredBeforeUse(declaration, location)) {
40669+
error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums);
40670+
return 0;
40671+
}
40672+
return getEnumMemberValue(declaration as EnumMember);
4067840673
}
4067940674

4068040675
function checkEnumDeclaration(node: EnumDeclaration) {

0 commit comments

Comments
 (0)