-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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 3 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -489,10 +489,12 @@ module ts { | |
} | ||
|
||
// Resolves a qualified name and any involved import aliases | ||
function resolveEntityName(location: Node, name: EntityName, meaning: SymbolFlags): Symbol { | ||
function resolveEntityName(location: Node, name: EntityName, meaning: SymbolFlags, suppressErrors?: boolean): Symbol { | ||
if (name.kind === SyntaxKind.Identifier) { | ||
// TODO: Investigate error recovery for symbols not found | ||
var symbol = resolveName(location, (<Identifier>name).text, meaning, Diagnostics.Cannot_find_name_0, identifierToString(<Identifier>name)); | ||
var nameNotFoundMessage = !suppressErrors && Diagnostics.Cannot_find_name_0; | ||
var nameArg = !suppressErrors && identifierToString(<Identifier>name); | ||
var symbol = resolveName(location, (<Identifier>name).text, meaning, nameNotFoundMessage, nameArg); | ||
if (!symbol) { | ||
return; | ||
} | ||
|
@@ -502,8 +504,10 @@ module ts { | |
if (!namespace || namespace === unknownSymbol || (<QualifiedName>name).right.kind === SyntaxKind.Missing) return; | ||
var symbol = getSymbol(namespace.exports, (<QualifiedName>name).right.text, meaning); | ||
if (!symbol) { | ||
error(location, Diagnostics.Module_0_has_no_exported_member_1, getFullyQualifiedName(namespace), | ||
identifierToString((<QualifiedName>name).right)); | ||
if (!suppressErrors) { | ||
error(location, Diagnostics.Module_0_has_no_exported_member_1, getFullyQualifiedName(namespace), | ||
identifierToString((<QualifiedName>name).right)); | ||
} | ||
return; | ||
} | ||
} | ||
|
@@ -7397,23 +7401,6 @@ module ts { | |
} | ||
} | ||
|
||
function getConstantValueForExpression(node: Expression): number { | ||
var isNegative = false; | ||
if (node.kind === SyntaxKind.PrefixOperator) { | ||
var unaryExpression = <UnaryExpression>node; | ||
if (unaryExpression.operator === SyntaxKind.MinusToken || unaryExpression.operator === SyntaxKind.PlusToken) { | ||
node = unaryExpression.operand; | ||
isNegative = unaryExpression.operator === SyntaxKind.MinusToken; | ||
} | ||
} | ||
if (node.kind === SyntaxKind.NumericLiteral) { | ||
var literalText = (<LiteralExpression>node).text; | ||
return isNegative ? -literalText : +literalText; | ||
} | ||
|
||
return undefined; | ||
} | ||
|
||
function computeEnumMemberValues(node: EnumDeclaration) { | ||
var nodeLinks = getNodeLinks(node); | ||
|
||
|
@@ -7423,13 +7410,13 @@ module ts { | |
var autoValue = 0; | ||
var ambient = isInAmbientContext(node); | ||
|
||
forEach(node.members, member => { | ||
forEach(node.members, member => { | ||
if(isNumericName(member.name.text)) { | ||
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name); | ||
} | ||
var initializer = member.initializer; | ||
if (initializer) { | ||
autoValue = getConstantValueForExpression(initializer); | ||
autoValue = getConstantValueForEnumMemberInitializer(member, 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. passing member and initializer seems redundant. 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. done |
||
if (autoValue === undefined && !ambient) { | ||
// Only here do we need to check that the initializer is assignable to the enum type. | ||
// If it is a constant value (not undefined), it is syntactically constrained to be a number. | ||
|
@@ -7449,6 +7436,71 @@ module ts { | |
|
||
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; | ||
} | ||
|
||
function getConstantValueForEnumMemberInitializer(member: EnumMember, initializer: Expression): number { | ||
return evalConstant(initializer); | ||
|
||
function evalConstant(e: Node): number { | ||
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. Seems like all you're getting from introducing a helper function is a nicer name - it's a little unnecessary, but I don't mind it all that much. |
||
switch (e.kind) { | ||
case SyntaxKind.PrefixOperator: | ||
var value = evalConstant((<UnaryExpression>e).operand); | ||
if (value === undefined) return undefined; | ||
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. curlies+newline. 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. ok |
||
switch ((<UnaryExpression>e).operator) { | ||
case SyntaxKind.PlusToken: return value; | ||
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. these are new capabilities not previously in the compiler. do we intend to put these behind a flag? 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. not really, we used to handle UnaryExpression with '+' and '-' as operators. I'll cover '~' by the flag |
||
case SyntaxKind.MinusToken: return -value; | ||
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. note: the spec (at one point) specified that this was a signed-integer. If so, then there can be no space between the |
||
case SyntaxKind.TildeToken: return ~value; | ||
} | ||
return undefined; | ||
case SyntaxKind.BinaryExpression: | ||
if (!program.getCompilerOptions().propagateEnumConstants) return undefined; | ||
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. curlies. |
||
|
||
var left = evalConstant((<BinaryExpression>e).left); | ||
if (left === undefined) return undefined; | ||
var right = evalConstant((<BinaryExpression>e).right); | ||
if (right === undefined) return undefined; | ||
switch ((<BinaryExpression>e).operator) { | ||
case SyntaxKind.BarToken: return left | right; | ||
case SyntaxKind.AmpersandToken: return left & right; | ||
case SyntaxKind.PlusToken: return left + right; | ||
case SyntaxKind.MinusToken: return left - right; | ||
case SyntaxKind.GreaterThanGreaterThanToken: return left >> right; | ||
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. Should support >>> as well probably 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. done 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. Why not include *, /, %, and ^ as well? Seems hard to define a principle that would exclude them. 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. 👍 to Anders' suggestion. Might be arguable as to what you want to do when the RHS for |
||
case SyntaxKind.LessThanLessThanToken: return left << right; | ||
} | ||
return undefined; | ||
case SyntaxKind.NumericLiteral: | ||
return +(<LiteralExpression>e).text; | ||
case SyntaxKind.Identifier: | ||
case SyntaxKind.PropertyAccess: | ||
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. we probably want to handle IndexedAccess as well. That way we can deal with enums with string/numeric names. |
||
if (!program.getCompilerOptions().propagateEnumConstants) return undefined; | ||
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. curlies 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. done |
||
|
||
var enumSymbol: Symbol; | ||
var 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 symbol that correspond of enum declaration and later try to fetch member from the symbol | ||
enumSymbol = getSymbolOfNode(member.parent); | ||
propertyName = (<Identifier>e).text; | ||
} | ||
else { | ||
// left part in PropertyAccess should be resolved to the symbol of enum that declared 'member' | ||
enumSymbol = resolveEntityName(member, (<PropertyAccess>e).left, SymbolFlags.Enum, /*suppressErrors*/ true); | ||
|
||
if (enumSymbol !== getSymbolOfNode(member.parent)) return undefined; | ||
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. curlies. |
||
propertyName = (<Identifier>(<PropertyAccess>e).right).text; | ||
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. cast to identifier shoudln't be necessary. |
||
} | ||
|
||
var propertySymbol = enumSymbol.exports[propertyName]; | ||
if (!propertyName || !(propertySymbol.flags & SymbolFlags.EnumMember)) return undefined; | ||
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. enum property name could be "" right? i think you need to check that propertyName is actually undefined. |
||
var propertyDecl = <EnumMember>propertySymbol.valueDeclaration; | ||
// self references are illegal | ||
if (member === propertyDecl) return undefined; | ||
// enumMemberValue might be undefined if corresponding enum value was not yet computed | ||
// and it is ok to return undefined in this case (use before defition) | ||
return <number>getNodeLinks(propertyDecl).enumMemberValue; | ||
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. Will this work ok in the LS (where we might helicopter in to an arbitrary enum member)? 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 believe that this value was never used by language service. In fact we do not compute them when checker is not in fulltypecheck mode. |
||
} | ||
} | ||
} | ||
} | ||
|
||
function checkEnumDeclaration(node: EnumDeclaration) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,7 @@ module ts { | |
type: "boolean", | ||
}, | ||
{ | ||
name: "emitBOM", | ||
name: "emitBOM", | ||
type: "boolean" | ||
}, | ||
{ | ||
|
@@ -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 | ||
|
@@ -118,6 +118,11 @@ module ts { | |
shortName: "w", | ||
type: "boolean", | ||
description: Diagnostics.Watch_input_files, | ||
}, | ||
{ | ||
name: "propagateEnumConstants", | ||
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 this should be marked as 'experimental'. Or something to indicate that it is a spec augmentation. 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 not put description on it so that it does not show in the main list of options. |
||
type: "boolean", | ||
description: Diagnostics.Propagate_constant_values_in_enum_member_initializers | ||
} | ||
]; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
//// [constantsInEnumMembers.ts] | ||
|
||
enum Enum1 { | ||
A0 = 100, | ||
} | ||
|
||
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, | ||
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. can you have a version with a more complicated property access? like x.y.Enum1.A0. |
||
|
||
// illegal case | ||
// forward reference to the element of the same enum | ||
X = Y, | ||
// forward reference to the element of the same enum | ||
Y = Enum1.Z, | ||
Z = 100, | ||
} | ||
|
||
|
||
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.X: | ||
case Enum1.Y: | ||
case Enum1.Z: | ||
break; | ||
} | ||
} | ||
|
||
//// [constantsInEnumMembers.js] | ||
var Enum1; | ||
(function (Enum1) { | ||
Enum1[Enum1["A0"] = 100] = "A0"; | ||
})(Enum1 || (Enum1 = {})); | ||
var Enum1; | ||
(function (Enum1) { | ||
// correct cases | ||
Enum1[Enum1["A"] = 0] = "A"; | ||
Enum1[Enum1["B"] = 1] = "B"; | ||
Enum1[Enum1["C"] = 10] = "C"; | ||
Enum1[Enum1["D"] = A + B] = "D"; | ||
Enum1[Enum1["E"] = A + 1] = "E"; | ||
Enum1[Enum1["F"] = 1 + A] = "F"; | ||
Enum1[Enum1["G"] = 1 + 1] = "G"; | ||
Enum1[Enum1["H"] = A - B] = "H"; | ||
Enum1[Enum1["I"] = A - 1] = "I"; | ||
Enum1[Enum1["J"] = 1 - A] = "J"; | ||
Enum1[Enum1["K"] = 1 - 1] = "K"; | ||
Enum1[Enum1["L"] = ~D] = "L"; | ||
Enum1[Enum1["M"] = E << B] = "M"; | ||
Enum1[Enum1["N"] = E << 1] = "N"; | ||
Enum1[Enum1["O"] = E >> B] = "O"; | ||
Enum1[Enum1["P"] = E >> 1] = "P"; | ||
Enum1[Enum1["Q"] = -D] = "Q"; | ||
Enum1[Enum1["R"] = C & 5] = "R"; | ||
Enum1[Enum1["S"] = 5 & C] = "S"; | ||
Enum1[Enum1["T"] = C | D] = "T"; | ||
Enum1[Enum1["U"] = C | 1] = "U"; | ||
Enum1[Enum1["V"] = 10 | D] = "V"; | ||
Enum1[Enum1["W"] = Enum1.V] = "W"; | ||
// correct cases: reference to the enum member from different enum declaration | ||
Enum1[Enum1["W1"] = A0] = "W1"; | ||
Enum1[Enum1["W2"] = Enum1.A0] = "W2"; | ||
// illegal case | ||
// forward reference to the element of the same enum | ||
Enum1[Enum1["X"] = Enum1.Y] = "X"; | ||
// forward reference to the element of the same enum | ||
Enum1[Enum1["Y"] = 100 /* Z */] = "Y"; | ||
Enum1[Enum1["Z"] = 100] = "Z"; | ||
})(Enum1 || (Enum1 = {})); | ||
function foo(x) { | ||
switch (x) { | ||
case 0 /* A */: | ||
case 1 /* B */: | ||
case 10 /* C */: | ||
case 1 /* D */: | ||
case 1 /* E */: | ||
case 1 /* F */: | ||
case 2 /* G */: | ||
case -1 /* H */: | ||
case -1 /* I */: | ||
case 1 /* J */: | ||
case 0 /* K */: | ||
case -2 /* L */: | ||
case 2 /* M */: | ||
case 2 /* N */: | ||
case 0 /* O */: | ||
case 0 /* P */: | ||
case -1 /* Q */: | ||
case 0 /* R */: | ||
case 0 /* S */: | ||
case 11 /* T */: | ||
case 11 /* U */: | ||
case 11 /* V */: | ||
case 11 /* W */: | ||
case 100 /* W1 */: | ||
case 100 /* W2 */: | ||
case Enum1.X: | ||
case Enum1.Y: | ||
case 100 /* Z */: | ||
break; | ||
} | ||
} |
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.
errant space.
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.
done