Skip to content

Const enums #970

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Nov 3, 2014
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0d171ca
initial implementation of constant folding
vladima Oct 26, 2014
97460f5
handle non-qualified names, add 'propagateEnumConstants' command line…
vladima Oct 26, 2014
ce336bc
added folding for references to enum members in enum member initializ…
vladima Oct 26, 2014
365587f
addressed CR feedback, added support for indexed access
vladima Oct 27, 2014
cb472eb
move code around to consolidate checks in one place
vladima Oct 27, 2014
03cb645
dropped redundand type assertion, added mising check
vladima Oct 27, 2014
329d6e2
merge with master
vladima Oct 28, 2014
2dd9511
'const enum' iteration 0. TODO: allow and track const enums in import…
vladima Oct 28, 2014
6f4ea86
merge with master
vladima Oct 29, 2014
e949eda
const enums, iteration 1: const enums can be used in imports, const e…
vladima Oct 29, 2014
4aa4ea7
allow arithmetic operations in constant expressions, handle infinity\…
vladima Oct 30, 2014
270d187
addressed CR feedback
vladima Oct 30, 2014
dd57c6c
added .d.ts generation tests
vladima Oct 31, 2014
ac54fbf
set 'earlyError' bit to 'non-constant expression in constant enum ini…
vladima Oct 31, 2014
7d80b71
do not treat module that contains only const enums as instantiated
vladima Nov 1, 2014
8662c68
add test for 'preserveConstEnums' command line argument
vladima Nov 1, 2014
0b738e8
merge with master
vladima Nov 1, 2014
2d94030
inline enum constant values for indexed access when index is string l…
vladima Nov 2, 2014
4d354c0
addressed CR feedback: adjusted text of error messages, added descrip…
vladima Nov 3, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 168 additions & 41 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module ts {
type: "boolean",
},
{
name: "emitBOM",
name: "emitBOM",
type: "boolean"
},
{
Expand Down Expand Up @@ -102,7 +102,7 @@ module ts {
{
name: "target",
shortName: "t",
type: { "es3": ScriptTarget.ES3, "es5": ScriptTarget.ES5 , "es6": ScriptTarget.ES6 },
type: { "es3": ScriptTarget.ES3, "es5": ScriptTarget.ES5, "es6": ScriptTarget.ES6 },
description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_or_ES6_experimental,
paramType: Diagnostics.VERSION,
error: Diagnostics.Argument_for_target_option_must_be_es3_es5_or_es6
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,9 @@ module ts {
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." },
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}'." },
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}'." },
Enum_declarations_must_all_be_const_or_non_const: { code: 4079, category: DiagnosticCategory.Error, key: "Enum declarations must all be const or non-const." },
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." },
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." },
The_current_host_does_not_support_the_0_option: { code: 5001, category: DiagnosticCategory.Error, key: "The current host does not support the '{0}' option." },
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." },
Cannot_read_file_0_Colon_1: { code: 5012, category: DiagnosticCategory.Error, key: "Cannot read file '{0}': {1}" },
Expand Down
15 changes: 12 additions & 3 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1409,13 +1409,22 @@
"category": "Error",
"code": 4078
},


"Enum declarations must all be const or non-const.": {
"category": "Error",
"code": 4079
},
"In 'const' enum declarations member initializer must be constant expression.": {
"category": "Error",
"code": 4079
},
"'const' enums can only be used in property access expressions.": {
"category": "Error",
"code": 4079
},
"The current host does not support the '{0}' option.": {
"category": "Error",
"code": 5001
},

"Cannot find the common subdirectory path for the input files.": {
"category": "Error",
"code": 5009
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,9 @@ module ts {
}

function emitEnumDeclaration(node: EnumDeclaration) {
if (isConstEnumDeclaration(node)) {
return;
}
emitLeadingComments(node);
if (!(node.flags & NodeFlags.Export)) {
emitStart(node);
Expand Down Expand Up @@ -2570,6 +2573,9 @@ module ts {
if (resolver.isDeclarationVisible(node)) {
emitJsDocComments(node);
emitDeclarationFlags(node);
if (isConstEnumDeclaration(node)) {
write("const ")
}
write("enum ");
emitSourceTextOfNode(node.name);
write(" {");
Expand Down
49 changes: 38 additions & 11 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ module ts {
return (file.flags & NodeFlags.DeclarationFile) !== 0;
}

export function isConstEnumDeclaration(node: EnumDeclaration): boolean {
return (node.flags & NodeFlags.Const) !== 0;
}

export function isPrologueDirective(node: Node): boolean {
return node.kind === SyntaxKind.ExpressionStatement && (<ExpressionStatement>node).expression.kind === SyntaxKind.StringLiteral;
}
Expand Down Expand Up @@ -3146,7 +3150,6 @@ module ts {
case SyntaxKind.OpenBraceToken:
case SyntaxKind.VarKeyword:
case SyntaxKind.LetKeyword:
case SyntaxKind.ConstKeyword:
case SyntaxKind.FunctionKeyword:
case SyntaxKind.IfKeyword:
case SyntaxKind.DoKeyword:
Expand All @@ -3165,6 +3168,12 @@ module ts {
case SyntaxKind.CatchKeyword:
case SyntaxKind.FinallyKeyword:
return true;
case SyntaxKind.ConstKeyword:
// const keyword can precede enum keyword when defining constant enums
// 'const enum' do not start statement.
// In ES 6 'enum' is a future reserved keyword, so it should not be used as identifier
var isConstEnum = lookAhead(() => nextToken() === SyntaxKind.EnumKeyword);
return !isConstEnum;
case SyntaxKind.InterfaceKeyword:
case SyntaxKind.ClassKeyword:
case SyntaxKind.ModuleKeyword:
Expand All @@ -3174,6 +3183,7 @@ module ts {
if (isDeclaration()) {
return false;
}

case SyntaxKind.PublicKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
Expand All @@ -3195,6 +3205,7 @@ module ts {
case SyntaxKind.VarKeyword:
case SyntaxKind.LetKeyword:
case SyntaxKind.ConstKeyword:
// const here should always be parsed as const declaration because of check in 'isStatement'
return parseVariableStatement(allowLetAndConstDeclarations);
case SyntaxKind.FunctionKeyword:
return parseFunctionDeclaration();
Expand Down Expand Up @@ -3748,6 +3759,7 @@ module ts {
}

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

if (inAmbientContext) {
if (node.initializer && !isIntegerLiteral(node.initializer) && errorCountBeforeEnumMember === file.syntacticErrors.length) {
grammarErrorOnNode(node.name, Diagnostics.Ambient_enum_elements_can_only_have_integer_literal_initializers);
// skip checks below for const enums - they allow arbitrary initializers as long as they can be evaluated to constant expressions.
// since all values are known in compile time - it is not necessary to check that constant enum section precedes computed enum members.
if (!enumIsConst) {
if (inAmbientContext) {
if (node.initializer && !isIntegerLiteral(node.initializer) && errorCountBeforeEnumMember === file.syntacticErrors.length) {
grammarErrorOnNode(node.name, Diagnostics.Ambient_enum_elements_can_only_have_integer_literal_initializers);
}
}
else if (node.initializer) {
inConstantEnumMemberSection = isIntegerLiteral(node.initializer);
}
else if (!inConstantEnumMemberSection && errorCountBeforeEnumMember === file.syntacticErrors.length) {
grammarErrorOnNode(node.name, Diagnostics.Enum_member_must_have_initializer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting that for non-const ambient enums, we check that the initializers are integer literals, but for const, we do not check anything until typecheck. It seems like we might want to do syntactic validation that the const initializers are of the syntactic form of a constant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should discuss it: definitely we can try to validate that initializer syntactically includes only operations that can be performed in compile time however this won't remove need in check in typechecker (because parser cannot validate that A.B can indeed be constant expression).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, you would need to check in both places. It would just be more consistent with the integer literal check for ambients.

}
}
else if (node.initializer) {
inConstantEnumMemberSection = isIntegerLiteral(node.initializer);
}
else if (!inConstantEnumMemberSection && errorCountBeforeEnumMember === file.syntacticErrors.length) {
grammarErrorOnNode(node.name, Diagnostics.Enum_member_must_have_initializer);
}
return finishNode(node);
}

var node = <EnumDeclaration>createNode(SyntaxKind.EnumDeclaration, pos);
node.flags = flags;
if (enumIsConst) {
parseExpected(SyntaxKind.ConstKeyword);
}
parseExpected(SyntaxKind.EnumKeyword);
node.name = parseIdentifier();
if (parseExpected(SyntaxKind.OpenBraceToken)) {
Expand Down Expand Up @@ -3953,9 +3972,17 @@ module ts {
switch (token) {
case SyntaxKind.VarKeyword:
case SyntaxKind.LetKeyword:
case SyntaxKind.ConstKeyword:
result = parseVariableStatement(/*allowLetAndConstDeclarations*/ true, pos, flags);
break;
case SyntaxKind.ConstKeyword:
var isConstEnum = lookAhead(() => nextToken() === SyntaxKind.EnumKeyword);
if (isConstEnum) {
result = parseAndCheckEnumDeclaration(pos, flags | NodeFlags.Const);
}
else {
result = parseVariableStatement(/*allowLetAndConstDeclarations*/ true, pos, flags);
}
break;
case SyntaxKind.FunctionKeyword:
result = parseFunctionDeclaration(pos, flags);
break;
Expand Down
2 changes: 0 additions & 2 deletions src/harness/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,6 @@ module Harness {
case 'codepage':
case 'createFileLog':
case 'filename':
case 'propagateenumconstants':
case 'removecomments':
case 'watch':
case 'allowautomaticsemicoloninsertion':
Expand All @@ -772,7 +771,6 @@ module Harness {
case 'errortruncation':
options.noErrorTruncation = setting.value === 'false';
break;

default:
throw new Error('Unsupported compiler setting ' + setting.flag);
}
Expand Down
122 changes: 122 additions & 0 deletions tests/baselines/reference/constantsInEnumMembers.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
tests/cases/compiler/constantsInEnumMembers.ts(38,9): error TS4079: In 'const' enum declarations member initializer must be constant expression.
tests/cases/compiler/constantsInEnumMembers.ts(40,9): error TS4079: In 'const' enum declarations member initializer must be constant expression.
tests/cases/compiler/constantsInEnumMembers.ts(41,10): error TS4079: In 'const' enum declarations member initializer must be constant expression.


==== tests/cases/compiler/constantsInEnumMembers.ts (3 errors) ====
const enum Enum1 {
A0 = 100,
}

const enum Enum1 {
// correct cases
A,
B,
C = 10,
D = A + B,
E = A + 1,
F = 1 + A,
G = 1 + 1,
H = A - B,
I = A - 1,
J = 1 - A,
K = 1 - 1,
L = ~D,
M = E << B,
N = E << 1,
O = E >> B,
P = E >> 1,
Q = -D,
R = C & 5,
S = 5 & C,
T = C | D,
U = C | 1,
V = 10 | D,
W = Enum1.V,

// correct cases: reference to the enum member from different enum declaration
W1 = A0,
W2 = Enum1.A0,
W3 = Enum1["A0"],
W4 = Enum1["W"],
// illegal case
// forward reference to the element of the same enum
X = Y,
~
!!! error TS4079: In 'const' enum declarations member initializer must be constant expression.
// forward reference to the element of the same enum
Y = Enum1.Z,
~~~~~~~
!!! error TS4079: In 'const' enum declarations member initializer must be constant expression.
Y1 = Enum1["Z"],
~~~~~~~~~~
!!! error TS4079: In 'const' enum declarations member initializer must be constant expression.
Z = 100,
}


module A {
export module B {
export module C {
export const enum E {
V1 = 1,
V2 = A.B.C.E.V1 + 100
}
}
}
}

module A {
export module B {
export module C {
export const enum E {
V3 = A.B.C.E["V2"] + 200,
}
}
}
}

function foo(x: Enum1) {
switch (x) {
case Enum1.A:
case Enum1.B:
case Enum1.C:
case Enum1.D:
case Enum1.E:
case Enum1.F:
case Enum1.G:
case Enum1.H:
case Enum1.I:
case Enum1.J:
case Enum1.K:
case Enum1.L:
case Enum1.M:
case Enum1.N:
case Enum1.O:
case Enum1.P:
case Enum1.Q:
case Enum1.R:
case Enum1.S:
case Enum1.T:
case Enum1.U:
case Enum1.V:
case Enum1.W:
case Enum1.W1:
case Enum1.W2:
case Enum1.W3:
case Enum1.W4:
case Enum1.X:
case Enum1.Y:
case Enum1.Y1:
case Enum1.Z:
break;
}
}

function bar(e: A.B.C.E): number {
switch (e) {
case A.B.C.E.V1: return 1;
case A.B.C.E.V2: return 1;
case A.B.C.E.V3: return 1;
}
}
Loading