Skip to content

Commit e949eda

Browse files
committed
const enums, iteration 1: const enums can be used in imports, const enums can be merged only with const enums.
1 parent 6f4ea86 commit e949eda

16 files changed

+703
-233
lines changed

src/compiler/binder.ts

+14-7
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,25 @@
55

66
module ts {
77

8-
export function isInstantiated(node: Node): boolean {
8+
export function isInstantiated(node: Node, checkConstEnums: boolean): boolean {
99
// A module is uninstantiated if it contains only
1010
// 1. interface declarations
1111
if (node.kind === SyntaxKind.InterfaceDeclaration) {
1212
return false;
1313
}
14-
// 2. non - exported import declarations
14+
// 2. const enum declarations don't make module instantiated
15+
else if (checkConstEnums && node.kind === SyntaxKind.EnumDeclaration && isConstEnumDeclaration(<EnumDeclaration>node)) {
16+
return false;
17+
}
18+
// 3. non - exported import declarations
1519
else if (node.kind === SyntaxKind.ImportDeclaration && !(node.flags & NodeFlags.Export)) {
1620
return false;
1721
}
18-
// 3. other uninstantiated module declarations.
19-
else if (node.kind === SyntaxKind.ModuleBlock && !forEachChild(node, isInstantiated)) {
22+
// 4. other uninstantiated module declarations.
23+
else if (node.kind === SyntaxKind.ModuleBlock && !forEachChild(node, n => isInstantiated(n, checkConstEnums))) {
2024
return false;
2125
}
22-
else if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated((<ModuleDeclaration>node).body)) {
26+
else if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated((<ModuleDeclaration>node).body, checkConstEnums)) {
2327
return false;
2428
}
2529
else {
@@ -248,7 +252,7 @@ module ts {
248252
if (node.name.kind === SyntaxKind.StringLiteral) {
249253
bindDeclaration(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes, /*isBlockScopeContainer*/ true);
250254
}
251-
else if (isInstantiated(node)) {
255+
else if (isInstantiated(node, /*checkConstEnums*/ false)) {
252256
bindDeclaration(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes, /*isBlockScopeContainer*/ true);
253257
}
254258
else {
@@ -364,7 +368,10 @@ module ts {
364368
bindDeclaration(<Declaration>node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes, /*isBlockScopeContainer*/ false);
365369
break;
366370
case SyntaxKind.EnumDeclaration:
367-
bindDeclaration(<Declaration>node, SymbolFlags.Enum, SymbolFlags.EnumExcludes, /*isBlockScopeContainer*/ false);
371+
var enumIsConst = isConstEnumDeclaration(<EnumDeclaration>node);
372+
var flags = enumIsConst ? SymbolFlags.Enum | SymbolFlags.ConstEnum : SymbolFlags.Enum;
373+
var excludes = enumIsConst ? SymbolFlags.ConstEnumExcludes : SymbolFlags.EnumExcludes;
374+
bindDeclaration(<Declaration>node, flags, excludes, /*isBlockScopeContainer*/ false);
368375
break;
369376
case SyntaxKind.ModuleDeclaration:
370377
bindModuleDeclaration(<ModuleDeclaration>node);

src/compiler/checker.ts

+38-23
Original file line numberDiff line numberDiff line change
@@ -4431,8 +4431,8 @@ module ts {
44314431

44324432
if (symbol.flags & SymbolFlags.Import) {
44334433
// Mark the import as referenced so that we emit it in the final .js file.
4434-
// exception: identifiers that appear in type queries
4435-
getSymbolLinks(symbol).referenced = !isInTypeQuery(node);
4434+
// exception: identifiers that appear in type queries, const enums
4435+
getSymbolLinks(symbol).referenced = !isInTypeQuery(node) && !isConstEnumSymbol(resolveImport(symbol));
44364436
}
44374437

44384438
checkCollisionWithCapturedSuperVariable(node, node);
@@ -5086,6 +5086,10 @@ module ts {
50865086

50875087
if (objectType === unknownType) return unknownType;
50885088

5089+
if (isConstEnumType(objectType) && node.index.kind !== SyntaxKind.StringLiteral) {
5090+
error(node.index, Diagnostics.Index_expression_arguments_in_const_enums_must_be_of_type_string);
5091+
}
5092+
50895093
// TypeScript 1.0 spec (April 2014): 4.10 Property Access
50905094
// - If IndexExpr is a string literal or a numeric literal and ObjExpr's apparent type has a property with the name
50915095
// given by that literal(converted to its string representation in the case of a numeric literal), the property access is of the type of that property.
@@ -5962,6 +5966,14 @@ module ts {
59625966
return (type.flags & TypeFlags.Structured) !== 0;
59635967
}
59645968

5969+
function isConstEnumType(type: Type) : boolean {
5970+
return type.flags & (TypeFlags.ObjectType | TypeFlags.Anonymous) && type.symbol && isConstEnumSymbol(type.symbol);
5971+
}
5972+
5973+
function isConstEnumSymbol(symbol: Symbol): boolean {
5974+
return (symbol.flags & SymbolFlags.ConstEnum) !== 0;
5975+
}
5976+
59655977
function checkInstanceOfExpression(node: BinaryExpression, leftType: Type, rightType: Type): Type {
59665978
// TypeScript 1.0 spec (April 2014): 4.15.4
59675979
// The instanceof operator requires the left operand to be of type Any, an object type, or a type parameter type,
@@ -6187,19 +6199,31 @@ module ts {
61876199
}
61886200
}
61896201

6190-
if (type.flags & (TypeFlags.ObjectType | TypeFlags.Anonymous) &&
6191-
type.symbol &&
6192-
(type.symbol.flags & SymbolFlags.Enum) &&
6193-
isConstEnumDeclaration(<EnumDeclaration>type.symbol.valueDeclaration)) {
6194-
// enum object type for const enums are only permitted in as 'left' in property access and 'object' in indexed access
6202+
if (isConstEnumType(type)) {
6203+
// enum object type for const enums are only permitted in:
6204+
// - 'left' in property access
6205+
// - 'object' in indexed access
6206+
// - target in rhs of import statement
61956207
var ok =
61966208
(node.parent.kind === SyntaxKind.PropertyAccess && (<PropertyAccess>node.parent).left === node) ||
6197-
(node.parent.kind === SyntaxKind.IndexedAccess && (<IndexedAccess>node.parent).object === node);
6209+
(node.parent.kind === SyntaxKind.IndexedAccess && (<IndexedAccess>node.parent).object === node) ||
6210+
isRhsOfImportStatement(node);
6211+
61986212
if (!ok) {
61996213
error(node, Diagnostics.const_enums_can_only_be_used_in_property_access_expressions);
62006214
}
62016215
}
62026216
return type;
6217+
6218+
function isRhsOfImportStatement(n: Node): boolean {
6219+
while (n.parent) {
6220+
if (n.parent.kind === SyntaxKind.ImportDeclaration && (<ImportDeclaration>n.parent).entityName === n) {
6221+
return true;
6222+
}
6223+
n = n.parent;
6224+
}
6225+
return false;
6226+
}
62036227
}
62046228

62056229
function checkExpressionNode(node: Expression, contextualMapper: TypeMapper): Type {
@@ -6868,7 +6892,7 @@ module ts {
68686892
case SyntaxKind.InterfaceDeclaration:
68696893
return SymbolFlags.ExportType;
68706894
case SyntaxKind.ModuleDeclaration:
6871-
return (<ModuleDeclaration>d).name.kind === SyntaxKind.StringLiteral || isInstantiated(d)
6895+
return (<ModuleDeclaration>d).name.kind === SyntaxKind.StringLiteral || isInstantiated(d, /*checkConstEnums*/ false)
68726896
? SymbolFlags.ExportNamespace | SymbolFlags.ExportValue
68736897
: SymbolFlags.ExportNamespace;
68746898
case SyntaxKind.ClassDeclaration:
@@ -7105,7 +7129,7 @@ module ts {
71057129
}
71067130

71077131
// Uninstantiated modules shouldnt do this check
7108-
if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated(node)) {
7132+
if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated(node, /*checkConstEnums*/ true)) {
71097133
return;
71107134
}
71117135

@@ -7707,14 +7731,9 @@ module ts {
77077731
switch ((<BinaryExpression>e).operator) {
77087732
case SyntaxKind.BarToken: return left | right;
77097733
case SyntaxKind.AmpersandToken: return left & right;
7710-
case SyntaxKind.PlusToken: return left + right;
7711-
case SyntaxKind.MinusToken: return left - right;
77127734
case SyntaxKind.GreaterThanGreaterThanToken: return left >> right;
77137735
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right;
77147736
case SyntaxKind.LessThanLessThanToken: return left << right;
7715-
case SyntaxKind.AsteriskToken: return left * right;
7716-
case SyntaxKind.SlashToken: return left / right;
7717-
case SyntaxKind.PercentToken: return left % right;
77187737
case SyntaxKind.CaretToken: return left ^ right;
77197738
}
77207739
return undefined;
@@ -7803,13 +7822,8 @@ module ts {
78037822
if (node === firstDeclaration) {
78047823
if (enumSymbol.declarations.length > 1) {
78057824
var enumIsConst = isConstEnumDeclaration(node);
7806-
// check that const is places\omitted on all enum declarations
7825+
// check that const is placed\omitted on all enum declarations
78077826
forEach(enumSymbol.declarations, decl => {
7808-
if (decl.kind !== SyntaxKind.EnumDeclaration) {
7809-
// TODO(vladima): do we want to allow merging for const enum declarations
7810-
return;
7811-
}
7812-
78137827
if (isConstEnumDeclaration(<EnumDeclaration>decl) !== enumIsConst) {
78147828
error(decl.name, Diagnostics.Enum_declarations_must_all_be_const_or_non_const);
78157829
}
@@ -8655,7 +8669,7 @@ module ts {
86558669
}
86568670
var symbol = getSymbolOfNode(node);
86578671
var target = resolveImport(symbol);
8658-
return target !== unknownSymbol && ((target.flags & SymbolFlags.Value) !== 0);
8672+
return target !== unknownSymbol && ((target.flags & SymbolFlags.Value) !== 0) && !isConstEnumSymbol(target);
86598673
}
86608674

86618675
function hasSemanticErrors() {
@@ -8676,7 +8690,8 @@ module ts {
86768690
// As a consequence this might cause emitting extra.
86778691
if (node.flags & NodeFlags.Export) {
86788692
var target = resolveImport(symbol);
8679-
if (target !== unknownSymbol && target.flags & SymbolFlags.Value) {
8693+
// importing const enum does not cause import to be referenced
8694+
if (target !== unknownSymbol && target.flags & SymbolFlags.Value && !isConstEnumSymbol(target)) {
86808695
return true;
86818696
}
86828697
}

src/compiler/commandLineParser.ts

+4
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ module ts {
118118
shortName: "w",
119119
type: "boolean",
120120
description: Diagnostics.Watch_input_files,
121+
},
122+
{
123+
name: "preserveConstEnums",
124+
type: "boolean"
121125
}
122126
];
123127

src/compiler/diagnosticInformationMap.generated.ts

+1
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ module ts {
353353
Enum_declarations_must_all_be_const_or_non_const: { code: 4082, category: DiagnosticCategory.Error, key: "Enum declarations must all be const or non-const." },
354354
In_const_enum_declarations_member_initializer_must_be_constant_expression: { code: 4083, category: DiagnosticCategory.Error, key: "In 'const' enum declarations member initializer must be constant expression." },
355355
const_enums_can_only_be_used_in_property_access_expressions: { code: 4084, category: DiagnosticCategory.Error, key: "'const' enums can only be used in property access expressions." },
356+
Index_expression_arguments_in_const_enums_must_be_of_type_string: { code: 4085, category: DiagnosticCategory.Error, key: "Index expression arguments in 'const' enums must be of type 'string'." },
356357
The_current_host_does_not_support_the_0_option: { code: 5001, category: DiagnosticCategory.Error, key: "The current host does not support the '{0}' option." },
357358
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." },
358359
Cannot_read_file_0_Colon_1: { code: 5012, category: DiagnosticCategory.Error, key: "Cannot read file '{0}': {1}" },

src/compiler/diagnosticMessages.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -1412,7 +1412,11 @@
14121412
"'const' enums can only be used in property access expressions.": {
14131413
"category": "Error",
14141414
"code": 4084
1415-
},
1415+
},
1416+
"Index expression arguments in 'const' enums must be of type 'string'.": {
1417+
"category": "Error",
1418+
"code": 4085
1419+
},
14161420
"The current host does not support the '{0}' option.": {
14171421
"category": "Error",
14181422
"code": 5001

src/compiler/emitter.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1760,7 +1760,8 @@ module ts {
17601760
}
17611761

17621762
function emitEnumDeclaration(node: EnumDeclaration) {
1763-
if (isConstEnumDeclaration(node)) {
1763+
// const enums are completely erased during compilation.
1764+
if (isConstEnumDeclaration(node) && !compilerOptions.preserveConstEnums) {
17641765
return;
17651766
}
17661767
emitLeadingComments(node);
@@ -1837,7 +1838,7 @@ module ts {
18371838
}
18381839

18391840
function emitModuleDeclaration(node: ModuleDeclaration) {
1840-
if (!isInstantiated(node)) {
1841+
if (!isInstantiated(node, /*checkConstEnums*/ true)) {
18411842
return emitPinnedOrTripleSlashComments(node);
18421843
}
18431844
emitLeadingComments(node);

src/compiler/types.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ module ts {
783783
Transient = 0x08000000, // Transient symbol (created during type check)
784784
Prototype = 0x10000000, // Prototype property (no source representation)
785785
UnionProperty = 0x20000000, // Property in union type
786+
ConstEnum = 0x40000000, // Const enum marker
786787

787788
Variable = FunctionScopedVariable | BlockScopedVariable,
788789
Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor,
@@ -807,7 +808,8 @@ module ts {
807808
ClassExcludes = (Value | Type) & ~ValueModule,
808809
InterfaceExcludes = Type & ~Interface,
809810
EnumExcludes = (Value | Type) & ~(Enum | ValueModule),
810-
ValueModuleExcludes = Value & ~(Function | Class | Enum | ValueModule),
811+
ConstEnumExcludes = (Value | Type) & ~Enum, // const enums merge only with enums
812+
ValueModuleExcludes = (Value | ConstEnum) & ~(Function | Class | Enum | ValueModule),
811813
NamespaceModuleExcludes = 0,
812814
MethodExcludes = Value & ~Method,
813815
GetAccessorExcludes = Value & ~SetAccessor,
@@ -1076,6 +1078,7 @@ module ts {
10761078
target?: ScriptTarget;
10771079
version?: boolean;
10781080
watch?: boolean;
1081+
preserveConstEnums?: boolean;
10791082
[option: string]: string | number | boolean;
10801083
}
10811084

src/services/breakpoints.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ module ts.BreakpointResolver {
178178

179179
case SyntaxKind.ModuleDeclaration:
180180
// span on complete module if it is instantiated
181-
if (!isInstantiated(node)) {
181+
if (!isInstantiated(node, /*checkConstEnums*/ true)) {
182182
return undefined;
183183
}
184184

@@ -350,7 +350,7 @@ module ts.BreakpointResolver {
350350
function spanInBlock(block: Block): TypeScript.TextSpan {
351351
switch (block.parent.kind) {
352352
case SyntaxKind.ModuleDeclaration:
353-
if (!isInstantiated(block.parent)) {
353+
if (!isInstantiated(block.parent, /*checkConstEnums*/ true)) {
354354
return undefined;
355355
}
356356

@@ -407,7 +407,7 @@ module ts.BreakpointResolver {
407407
switch (node.parent.kind) {
408408
case SyntaxKind.ModuleBlock:
409409
// If this is not instantiated module block no bp span
410-
if (!isInstantiated(node.parent.parent)) {
410+
if (!isInstantiated(node.parent.parent, /*checkConstEnums*/ true)) {
411411
return undefined;
412412
}
413413

src/services/services.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4549,7 +4549,7 @@ module ts {
45494549
if ((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral) {
45504550
return SemanticMeaning.Namespace | SemanticMeaning.Value;
45514551
}
4552-
else if (isInstantiated(node)) {
4552+
else if (isInstantiated(node, /*checkConstEnums*/ true)) {
45534553
return SemanticMeaning.Namespace | SemanticMeaning.Value;
45544554
}
45554555
else {
@@ -4826,7 +4826,7 @@ module ts {
48264826
*/
48274827
function hasValueSideModule(symbol: Symbol): boolean {
48284828
return forEach(symbol.declarations, declaration => {
4829-
return declaration.kind === SyntaxKind.ModuleDeclaration && isInstantiated(declaration);
4829+
return declaration.kind === SyntaxKind.ModuleDeclaration && isInstantiated(declaration, /*checkConstEnums*/ true);
48304830
});
48314831
}
48324832
}

0 commit comments

Comments
 (0)