Skip to content

Commit 2dd9511

Browse files
committed
'const enum' iteration 0. TODO: allow and track const enums in imports, add more tests
1 parent 329d6e2 commit 2dd9511

12 files changed

+242
-546
lines changed

src/compiler/checker.ts

+52-12
Original file line numberDiff line numberDiff line change
@@ -6152,6 +6152,19 @@ module ts {
61526152
}
61536153
}
61546154
}
6155+
6156+
if (type.flags & (TypeFlags.ObjectType | TypeFlags.Anonymous) &&
6157+
type.symbol &&
6158+
(type.symbol.flags & SymbolFlags.Enum) &&
6159+
isConstEnumDeclaration(<EnumDeclaration>type.symbol.valueDeclaration)) {
6160+
// enum object type for const enums are only permitted in as 'left' in property access and 'object' in indexed access
6161+
var ok =
6162+
(node.parent.kind === SyntaxKind.PropertyAccess && (<PropertyAccess>node.parent).left === node) ||
6163+
(node.parent.kind === SyntaxKind.IndexedAccess && (<IndexedAccess>node.parent).object === node);
6164+
if (!ok) {
6165+
error(node, Diagnostics.const_enums_can_only_be_used_in_property_access_expressions);
6166+
}
6167+
}
61556168
return type;
61566169
}
61576170

@@ -7590,23 +7603,29 @@ module ts {
75907603
var enumType = getDeclaredTypeOfSymbol(enumSymbol);
75917604
var autoValue = 0;
75927605
var ambient = isInAmbientContext(node);
7606+
var enumIsConst = isConstEnumDeclaration(node);
75937607

75947608
forEach(node.members, member => {
75957609
if(isNumericName(member.name.text)) {
75967610
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
75977611
}
75987612
var initializer = member.initializer;
75997613
if (initializer) {
7600-
autoValue = getConstantValueForEnumMemberInitializer(initializer);
7601-
if (autoValue === undefined && !ambient) {
7602-
// Only here do we need to check that the initializer is assignable to the enum type.
7603-
// If it is a constant value (not undefined), it is syntactically constrained to be a number.
7604-
// Also, we do not need to check this for ambients because there is already
7605-
// a syntax error if it is not a constant.
7606-
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined);
7614+
autoValue = getConstantValueForEnumMemberInitializer(initializer, enumIsConst);
7615+
if (autoValue === undefined) {
7616+
if (enumIsConst) {
7617+
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
7618+
}
7619+
else if (!ambient) {
7620+
// Only here do we need to check that the initializer is assignable to the enum type.
7621+
// If it is a constant value (not undefined), it is syntactically constrained to be a number.
7622+
// Also, we do not need to check this for ambients because there is already
7623+
// a syntax error if it is not a constant.
7624+
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined);
7625+
}
76077626
}
76087627
}
7609-
else if (ambient) {
7628+
else if (ambient && !enumIsConst) {
76107629
autoValue = undefined;
76117630
}
76127631

@@ -7618,7 +7637,7 @@ module ts {
76187637
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
76197638
}
76207639

7621-
function getConstantValueForEnumMemberInitializer(initializer: Expression): number {
7640+
function getConstantValueForEnumMemberInitializer(initializer: Expression, enumIsConst: boolean): number {
76227641
return evalConstant(initializer);
76237642

76247643
function evalConstant(e: Node): number {
@@ -7631,11 +7650,11 @@ module ts {
76317650
switch ((<UnaryExpression>e).operator) {
76327651
case SyntaxKind.PlusToken: return value;
76337652
case SyntaxKind.MinusToken: return -value;
7634-
case SyntaxKind.TildeToken: return compilerOptions.propagateEnumConstants ? ~value : undefined;
7653+
case SyntaxKind.TildeToken: return enumIsConst ? ~value : undefined;
76357654
}
76367655
return undefined;
76377656
case SyntaxKind.BinaryExpression:
7638-
if (!compilerOptions.propagateEnumConstants) {
7657+
if (!enumIsConst) {
76397658
return undefined;
76407659
}
76417660

@@ -7655,14 +7674,20 @@ module ts {
76557674
case SyntaxKind.GreaterThanGreaterThanToken: return left >> right;
76567675
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right;
76577676
case SyntaxKind.LessThanLessThanToken: return left << right;
7677+
case SyntaxKind.AsteriskToken: return left * right;
7678+
case SyntaxKind.SlashToken: return left / right;
7679+
case SyntaxKind.PercentToken: return left % right;
7680+
case SyntaxKind.CaretToken: return left ^ right;
76587681
}
76597682
return undefined;
76607683
case SyntaxKind.NumericLiteral:
76617684
return +(<LiteralExpression>e).text;
7685+
case SyntaxKind.ParenExpression:
7686+
return enumIsConst ? evalConstant((<ParenExpression>e).expression) : undefined;
76627687
case SyntaxKind.Identifier:
76637688
case SyntaxKind.IndexedAccess:
76647689
case SyntaxKind.PropertyAccess:
7665-
if (!compilerOptions.propagateEnumConstants) {
7690+
if (!enumIsConst) {
76667691
return undefined;
76677692
}
76687693

@@ -7738,6 +7763,21 @@ module ts {
77387763
var enumSymbol = getSymbolOfNode(node);
77397764
var firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind);
77407765
if (node === firstDeclaration) {
7766+
if (enumSymbol.declarations.length > 1) {
7767+
var enumIsConst = isConstEnumDeclaration(node);
7768+
// check that const is places\omitted on all enum declarations
7769+
forEach(enumSymbol.declarations, decl => {
7770+
if (decl.kind !== SyntaxKind.EnumDeclaration) {
7771+
// TODO(vladima): do we want to allow merging for const enum declarations
7772+
return;
7773+
}
7774+
7775+
if (isConstEnumDeclaration(<EnumDeclaration>decl) !== enumIsConst) {
7776+
error(decl.name, Diagnostics.Enum_declarations_must_all_be_const_or_non_const);
7777+
}
7778+
});
7779+
}
7780+
77417781
var seenEnumMissingInitialInitializer = false;
77427782
forEach(enumSymbol.declarations, declaration => {
77437783
// return true if we hit a violation of the rule, false otherwise

src/compiler/commandLineParser.ts

-5
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,6 @@ module ts {
118118
shortName: "w",
119119
type: "boolean",
120120
description: Diagnostics.Watch_input_files,
121-
},
122-
{
123-
name: "propagateEnumConstants",
124-
type: "boolean",
125-
description: Diagnostics.Propagate_constant_values_in_enum_member_initializers
126121
}
127122
];
128123

src/compiler/diagnosticInformationMap.generated.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,9 @@ module ts {
353353
Parameter_0_of_exported_function_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named: { code: 4076, category: DiagnosticCategory.Error, key: "Parameter '{0}' of exported function has or is using name '{1}' from external module {2} but cannot be named." },
354354
Parameter_0_of_exported_function_has_or_is_using_name_1_from_private_module_2: { code: 4077, category: DiagnosticCategory.Error, key: "Parameter '{0}' of exported function has or is using name '{1}' from private module '{2}'." },
355355
Parameter_0_of_exported_function_has_or_is_using_private_name_1: { code: 4078, category: DiagnosticCategory.Error, key: "Parameter '{0}' of exported function has or is using private name '{1}'." },
356+
Enum_declarations_must_all_be_const_or_non_const: { code: 4079, category: DiagnosticCategory.Error, key: "Enum declarations must all be const or non-const." },
357+
In_const_enum_declarations_member_initializer_must_be_constant_expression: { code: 4079, category: DiagnosticCategory.Error, key: "In 'const' enum declarations member initializer must be constant expression." },
358+
const_enums_can_only_be_used_in_property_access_expressions: { code: 4079, category: DiagnosticCategory.Error, key: "'const' enums can only be used in property access expressions." },
356359
The_current_host_does_not_support_the_0_option: { code: 5001, category: DiagnosticCategory.Error, key: "The current host does not support the '{0}' option." },
357360
Cannot_find_the_common_subdirectory_path_for_the_input_files: { code: 5009, category: DiagnosticCategory.Error, key: "Cannot find the common subdirectory path for the input files." },
358361
Cannot_read_file_0_Colon_1: { code: 5012, category: DiagnosticCategory.Error, key: "Cannot read file '{0}': {1}" },
@@ -372,7 +375,6 @@ module ts {
372375
Specify_module_code_generation_Colon_commonjs_or_amd: { code: 6016, category: DiagnosticCategory.Message, key: "Specify module code generation: 'commonjs' or 'amd'" },
373376
Print_this_message: { code: 6017, category: DiagnosticCategory.Message, key: "Print this message." },
374377
Print_the_compiler_s_version: { code: 6019, category: DiagnosticCategory.Message, key: "Print the compiler's version." },
375-
Propagate_constant_values_in_enum_member_initializers: { code: 6020, category: DiagnosticCategory.Message, key: "Propagate constant values in enum member initializers." },
376378
Syntax_Colon_0: { code: 6023, category: DiagnosticCategory.Message, key: "Syntax: {0}" },
377379
options: { code: 6024, category: DiagnosticCategory.Message, key: "options" },
378380
file: { code: 6025, category: DiagnosticCategory.Message, key: "file" },

src/compiler/diagnosticMessages.json

+12-7
Original file line numberDiff line numberDiff line change
@@ -1409,13 +1409,22 @@
14091409
"category": "Error",
14101410
"code": 4078
14111411
},
1412-
1413-
1412+
"Enum declarations must all be const or non-const.": {
1413+
"category": "Error",
1414+
"code": 4079
1415+
},
1416+
"In 'const' enum declarations member initializer must be constant expression.": {
1417+
"category": "Error",
1418+
"code": 4079
1419+
},
1420+
"'const' enums can only be used in property access expressions.": {
1421+
"category": "Error",
1422+
"code": 4079
1423+
},
14141424
"The current host does not support the '{0}' option.": {
14151425
"category": "Error",
14161426
"code": 5001
14171427
},
1418-
14191428
"Cannot find the common subdirectory path for the input files.": {
14201429
"category": "Error",
14211430
"code": 5009
@@ -1488,10 +1497,6 @@
14881497
"category": "Message",
14891498
"code": 6019
14901499
},
1491-
"Propagate constant values in enum member initializers.": {
1492-
"category": "Message",
1493-
"code": 6020
1494-
},
14951500
"Syntax: {0}": {
14961501
"category": "Message",
14971502
"code": 6023

src/compiler/emitter.ts

+6
Original file line numberDiff line numberDiff line change
@@ -1760,6 +1760,9 @@ module ts {
17601760
}
17611761

17621762
function emitEnumDeclaration(node: EnumDeclaration) {
1763+
if (isConstEnumDeclaration(node)) {
1764+
return;
1765+
}
17631766
emitLeadingComments(node);
17641767
if (!(node.flags & NodeFlags.Export)) {
17651768
emitStart(node);
@@ -2570,6 +2573,9 @@ module ts {
25702573
if (resolver.isDeclarationVisible(node)) {
25712574
emitJsDocComments(node);
25722575
emitDeclarationFlags(node);
2576+
if (isConstEnumDeclaration(node)) {
2577+
write("const ")
2578+
}
25732579
write("enum ");
25742580
emitSourceTextOfNode(node.name);
25752581
write(" {");

src/compiler/parser.ts

+38-11
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ module ts {
186186
return (file.flags & NodeFlags.DeclarationFile) !== 0;
187187
}
188188

189+
export function isConstEnumDeclaration(node: EnumDeclaration): boolean {
190+
return (node.flags & NodeFlags.Const) !== 0;
191+
}
192+
189193
export function isPrologueDirective(node: Node): boolean {
190194
return node.kind === SyntaxKind.ExpressionStatement && (<ExpressionStatement>node).expression.kind === SyntaxKind.StringLiteral;
191195
}
@@ -3146,7 +3150,6 @@ module ts {
31463150
case SyntaxKind.OpenBraceToken:
31473151
case SyntaxKind.VarKeyword:
31483152
case SyntaxKind.LetKeyword:
3149-
case SyntaxKind.ConstKeyword:
31503153
case SyntaxKind.FunctionKeyword:
31513154
case SyntaxKind.IfKeyword:
31523155
case SyntaxKind.DoKeyword:
@@ -3165,6 +3168,12 @@ module ts {
31653168
case SyntaxKind.CatchKeyword:
31663169
case SyntaxKind.FinallyKeyword:
31673170
return true;
3171+
case SyntaxKind.ConstKeyword:
3172+
// const keyword can precede enum keyword when defining constant enums
3173+
// 'const enum' do not start statement.
3174+
// In ES 6 'enum' is a future reserved keyword, so it should not be used as identifier
3175+
var isConstEnum = lookAhead(() => nextToken() === SyntaxKind.EnumKeyword);
3176+
return !isConstEnum;
31683177
case SyntaxKind.InterfaceKeyword:
31693178
case SyntaxKind.ClassKeyword:
31703179
case SyntaxKind.ModuleKeyword:
@@ -3174,6 +3183,7 @@ module ts {
31743183
if (isDeclaration()) {
31753184
return false;
31763185
}
3186+
31773187
case SyntaxKind.PublicKeyword:
31783188
case SyntaxKind.PrivateKeyword:
31793189
case SyntaxKind.ProtectedKeyword:
@@ -3195,6 +3205,7 @@ module ts {
31953205
case SyntaxKind.VarKeyword:
31963206
case SyntaxKind.LetKeyword:
31973207
case SyntaxKind.ConstKeyword:
3208+
// const here should always be parsed as const declaration because of check in 'isStatement'
31983209
return parseVariableStatement(allowLetAndConstDeclarations);
31993210
case SyntaxKind.FunctionKeyword:
32003211
return parseFunctionDeclaration();
@@ -3748,6 +3759,7 @@ module ts {
37483759
}
37493760

37503761
function parseAndCheckEnumDeclaration(pos: number, flags: NodeFlags): EnumDeclaration {
3762+
var enumIsConst = flags & NodeFlags.Const;
37513763
function isIntegerLiteral(expression: Expression): boolean {
37523764
function isInteger(literalExpression: LiteralExpression): boolean {
37533765
// Allows for scientific notation since literalExpression.text was formed by
@@ -3782,22 +3794,29 @@ module ts {
37823794
node.name = parsePropertyName();
37833795
node.initializer = parseInitializer(/*inParameter*/ false);
37843796

3785-
if (inAmbientContext) {
3786-
if (node.initializer && !isIntegerLiteral(node.initializer) && errorCountBeforeEnumMember === file.syntacticErrors.length) {
3787-
grammarErrorOnNode(node.name, Diagnostics.Ambient_enum_elements_can_only_have_integer_literal_initializers);
3797+
// skip checks below for const enums - they allow arbitrary initializers as long as they can be evaluated to constant expressions.
3798+
// since all values are known in compile time - it is not necessary to check that constant enum section precedes computed enum members.
3799+
if (!enumIsConst) {
3800+
if (inAmbientContext) {
3801+
if (node.initializer && !isIntegerLiteral(node.initializer) && errorCountBeforeEnumMember === file.syntacticErrors.length) {
3802+
grammarErrorOnNode(node.name, Diagnostics.Ambient_enum_elements_can_only_have_integer_literal_initializers);
3803+
}
3804+
}
3805+
else if (node.initializer) {
3806+
inConstantEnumMemberSection = isIntegerLiteral(node.initializer);
3807+
}
3808+
else if (!inConstantEnumMemberSection && errorCountBeforeEnumMember === file.syntacticErrors.length) {
3809+
grammarErrorOnNode(node.name, Diagnostics.Enum_member_must_have_initializer);
37883810
}
3789-
}
3790-
else if (node.initializer) {
3791-
inConstantEnumMemberSection = isIntegerLiteral(node.initializer);
3792-
}
3793-
else if (!inConstantEnumMemberSection && errorCountBeforeEnumMember === file.syntacticErrors.length) {
3794-
grammarErrorOnNode(node.name, Diagnostics.Enum_member_must_have_initializer);
37953811
}
37963812
return finishNode(node);
37973813
}
37983814

37993815
var node = <EnumDeclaration>createNode(SyntaxKind.EnumDeclaration, pos);
38003816
node.flags = flags;
3817+
if (enumIsConst) {
3818+
parseExpected(SyntaxKind.ConstKeyword);
3819+
}
38013820
parseExpected(SyntaxKind.EnumKeyword);
38023821
node.name = parseIdentifier();
38033822
if (parseExpected(SyntaxKind.OpenBraceToken)) {
@@ -3953,9 +3972,17 @@ module ts {
39533972
switch (token) {
39543973
case SyntaxKind.VarKeyword:
39553974
case SyntaxKind.LetKeyword:
3956-
case SyntaxKind.ConstKeyword:
39573975
result = parseVariableStatement(/*allowLetAndConstDeclarations*/ true, pos, flags);
39583976
break;
3977+
case SyntaxKind.ConstKeyword:
3978+
var isConstEnum = lookAhead(() => nextToken() === SyntaxKind.EnumKeyword);
3979+
if (isConstEnum) {
3980+
result = parseAndCheckEnumDeclaration(pos, flags | NodeFlags.Const);
3981+
}
3982+
else {
3983+
result = parseVariableStatement(/*allowLetAndConstDeclarations*/ true, pos, flags);
3984+
}
3985+
break;
39593986
case SyntaxKind.FunctionKeyword:
39603987
result = parseFunctionDeclaration(pos, flags);
39613988
break;

src/compiler/types.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1095,7 +1095,6 @@ module ts {
10951095
target?: ScriptTarget;
10961096
version?: boolean;
10971097
watch?: boolean;
1098-
propagateEnumConstants?: boolean;
10991098
[option: string]: any;
11001099
}
11011100

src/harness/harness.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -749,9 +749,6 @@ module Harness {
749749
case 'usecasesensitivefilenames':
750750
useCaseSensitiveFileNames = setting.value === 'true';
751751
break;
752-
case 'propagateenumconstants':
753-
options.propagateEnumConstants = setting.value === 'true';
754-
break;
755752

756753
case 'mapsourcefiles':
757754
case 'maproot':
@@ -1148,7 +1145,7 @@ module Harness {
11481145
var optionRegex = /^[\/]{2}\s*@(\w+)\s*:\s*(\S*)/gm; // multiple matches on multiple lines
11491146

11501147
// List of allowed metadata names
1151-
var fileMetadataNames = ["filename", "comments", "declaration", "module", "nolib", "sourcemap", "target", "out", "outdir", "noimplicitany", "noresolve", "newline", "newlines", "emitbom", "errortruncation", "usecasesensitivefilenames", "propagateenumconstants"];
1148+
var fileMetadataNames = ["filename", "comments", "declaration", "module", "nolib", "sourcemap", "target", "out", "outdir", "noimplicitany", "noresolve", "newline", "newlines", "emitbom", "errortruncation", "usecasesensitivefilenames"];
11521149

11531150
function extractCompilerSettings(content: string): CompilerSetting[] {
11541151

0 commit comments

Comments
 (0)