-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Const enums #970
Changes from 10 commits
0d171ca
97460f5
ce336bc
365587f
cb472eb
03cb645
329d6e2
2dd9511
6f4ea86
e949eda
4aa4ea7
270d187
dd57c6c
ac54fbf
7d80b71
8662c68
0b738e8
2d94030
4d354c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
} | ||
|
@@ -3150,7 +3154,6 @@ module ts { | |
case SyntaxKind.OpenBraceToken: | ||
case SyntaxKind.VarKeyword: | ||
case SyntaxKind.LetKeyword: | ||
case SyntaxKind.ConstKeyword: | ||
case SyntaxKind.FunctionKeyword: | ||
case SyntaxKind.IfKeyword: | ||
case SyntaxKind.DoKeyword: | ||
|
@@ -3169,6 +3172,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: | ||
|
@@ -3179,6 +3188,7 @@ module ts { | |
if (isDeclaration()) { | ||
return false; | ||
} | ||
|
||
case SyntaxKind.PublicKeyword: | ||
case SyntaxKind.PrivateKeyword: | ||
case SyntaxKind.ProtectedKeyword: | ||
|
@@ -3200,6 +3210,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(); | ||
|
@@ -3771,6 +3782,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 | ||
|
@@ -3805,22 +3817,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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) { | ||
|
@@ -3977,9 +3996,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; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -783,6 +783,7 @@ module ts { | |
Transient = 0x08000000, // Transient symbol (created during type check) | ||
Prototype = 0x10000000, // Prototype property (no source representation) | ||
UnionProperty = 0x20000000, // Property in union type | ||
ConstEnum = 0x40000000, // Const enum marker | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would change Enum to NormalEnum (or something like that). Then introduce a new member |
||
|
||
Variable = FunctionScopedVariable | BlockScopedVariable, | ||
Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor, | ||
|
@@ -807,7 +808,8 @@ module ts { | |
ClassExcludes = (Value | Type) & ~ValueModule, | ||
InterfaceExcludes = Type & ~Interface, | ||
EnumExcludes = (Value | Type) & ~(Enum | ValueModule), | ||
ValueModuleExcludes = Value & ~(Function | Class | Enum | ValueModule), | ||
ConstEnumExcludes = (Value | Type) & ~Enum, // const enums merge only with enums | ||
ValueModuleExcludes = (Value | ConstEnum) & ~(Function | Class | Enum | ValueModule), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see why ConstEnum can't merge with ValueModule There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now const enums can only be used in property access\index access so after erasure the leave nothing in the code. However let's say allow them to be merged with value modules.
Handling such cases will definitely make code more complicated and don't think it is worth it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough |
||
NamespaceModuleExcludes = 0, | ||
MethodExcludes = Value & ~Method, | ||
GetAccessorExcludes = Value & ~SetAccessor, | ||
|
@@ -1076,6 +1078,7 @@ module ts { | |
target?: ScriptTarget; | ||
version?: boolean; | ||
watch?: boolean; | ||
preserveConstEnums?: boolean; | ||
[option: string]: string | number | boolean; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not write this as:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't have strong preferences regarding one way or the other but ok :)