From 0d171ca9a10fa8b5ed241abdcdfe79930b52b2ff Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Sat, 25 Oct 2014 17:03:43 -0700 Subject: [PATCH 01/16] initial implementation of constant folding --- src/compiler/checker.ts | 75 ++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7a959c0dbfc4a..572e97eac67e8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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, (name).text, meaning, Diagnostics.Cannot_find_name_0, identifierToString(name)); + var nameNotFoundMessage = !suppressErrors && Diagnostics.Cannot_find_name_0; + var nameArg = !suppressErrors && identifierToString(name); + var symbol = resolveName(location, (name).text, meaning, nameNotFoundMessage, nameArg); if (!symbol) { return; } @@ -502,8 +504,10 @@ module ts { if (!namespace || namespace === unknownSymbol || (name).right.kind === SyntaxKind.Missing) return; var symbol = getSymbol(namespace.exports, (name).right.text, meaning); if (!symbol) { - error(location, Diagnostics.Module_0_has_no_exported_member_1, getFullyQualifiedName(namespace), - identifierToString((name).right)); + if (!suppressErrors) { + error(location, Diagnostics.Module_0_has_no_exported_member_1, getFullyQualifiedName(namespace), + identifierToString((name).right)); + } return; } } @@ -7397,23 +7401,6 @@ module ts { } } - function getConstantValueForExpression(node: Expression): number { - var isNegative = false; - if (node.kind === SyntaxKind.PrefixOperator) { - var 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 = (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); 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,48 @@ module ts { nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; } + + function getConstantValueForEnumMemberInitializer(member: EnumMember, initializer: Expression): number { + return evalConstant(initializer); + + function evalConstant(e: Node): number { + switch (e.kind) { + case SyntaxKind.PrefixOperator: + var value = evalConstant((e).operand); + if (value === undefined) return undefined; + switch ((e).operator) { + case SyntaxKind.PlusToken: return value; + case SyntaxKind.MinusToken: return -value; + case SyntaxKind.TildeToken: return ~value; + } + return undefined; + case SyntaxKind.BinaryExpression: + var left = evalConstant((e).left); + if (left === undefined) return undefined; + var right = evalConstant((e).right); + if (right === undefined) return undefined; + switch ((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; + case SyntaxKind.LessThanLessThanToken: return left << right; + } + return undefined; + case SyntaxKind.NumericLiteral: + return +(e).text; + case SyntaxKind.PropertyAccess: + var refSymbol = resolveEntityName(member, e, SymbolFlags.EnumMember, /*suppressErrors*/ true); + if (!refSymbol) return undefined; + var refDecl = refSymbol.valueDeclaration; + // self references are not permitted + if (member === refDecl) return undefined; + // enumMemberValue might be undefined if corresponding enum value was not yet computed and it is ok to return undefined in this case + return getNodeLinks(refDecl).enumMemberValue; + } + } + } } function checkEnumDeclaration(node: EnumDeclaration) { From 97460f51239e1e05e2dcd9a9d5a9ddbbd1180d25 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Sat, 25 Oct 2014 17:26:24 -0700 Subject: [PATCH 02/16] handle non-qualified names, add 'propagateEnumConstants' command line option --- src/compiler/checker.ts | 14 ++++++++++++-- src/compiler/commandLineParser.ts | 9 +++++++-- src/compiler/diagnosticInformationMap.generated.ts | 1 + src/compiler/diagnosticMessages.json | 4 ++++ src/compiler/types.ts | 1 + 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 572e97eac67e8..435787405a130 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7452,6 +7452,8 @@ module ts { } return undefined; case SyntaxKind.BinaryExpression: + if (!program.getCompilerOptions().propagateEnumConstants) return undefined; + var left = evalConstant((e).left); if (left === undefined) return undefined; var right = evalConstant((e).right); @@ -7467,12 +7469,20 @@ module ts { return undefined; case SyntaxKind.NumericLiteral: return +(e).text; + case SyntaxKind.Identifier: case SyntaxKind.PropertyAccess: - var refSymbol = resolveEntityName(member, e, SymbolFlags.EnumMember, /*suppressErrors*/ true); + if (!program.getCompilerOptions().propagateEnumConstants) return undefined; + + var refSymbol = + e.kind === SyntaxKind.Identifier + ? resolveName(member, (e).text, SymbolFlags.EnumMember, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined) + : resolveEntityName(member, e, SymbolFlags.EnumMember, /*suppressErrors*/ true); + if (!refSymbol) return undefined; var refDecl = refSymbol.valueDeclaration; // self references are not permitted - if (member === refDecl) return undefined; + // non-qualified names are permitted only to members defined in the same enum + if (member === refDecl || (e.kind === SyntaxKind.Identifier && refDecl.parent !== member.parent)) return undefined; // enumMemberValue might be undefined if corresponding enum value was not yet computed and it is ok to return undefined in this case return getNodeLinks(refDecl).enumMemberValue; } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index a7993484c647d..667102ce31571 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -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", + type: "boolean", + description: Diagnostics.Propagate_constant_values_in_enum_member_initializers } ]; diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index a5dda0b1f4b71..4b0db71d05102 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -369,6 +369,7 @@ module ts { Specify_module_code_generation_Colon_commonjs_or_amd: { code: 6016, category: DiagnosticCategory.Message, key: "Specify module code generation: 'commonjs' or 'amd'" }, Print_this_message: { code: 6017, category: DiagnosticCategory.Message, key: "Print this message." }, Print_the_compiler_s_version: { code: 6019, category: DiagnosticCategory.Message, key: "Print the compiler's version." }, + Propagate_constant_values_in_enum_member_initializers: { code: 6020, category: DiagnosticCategory.Message, key: "Propagate constant values in enum member initializers." }, Syntax_Colon_0: { code: 6023, category: DiagnosticCategory.Message, key: "Syntax: {0}" }, options: { code: 6024, category: DiagnosticCategory.Message, key: "options" }, file: { code: 6025, category: DiagnosticCategory.Message, key: "file" }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 1336af21aaf80..7132fc8feef0e 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1476,6 +1476,10 @@ "category": "Message", "code": 6019 }, + "Propagate constant values in enum member initializers.": { + "category": "Message", + "code": 6020 + }, "Syntax: {0}": { "category": "Message", "code": 6023 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index aa2ca3b4ba83a..7396b4a337b4e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1093,6 +1093,7 @@ module ts { target?: ScriptTarget; version?: boolean; watch?: boolean; + propagateEnumConstants?: boolean; [option: string]: any; } From ce336bcec78bf703aade06c6395354f526ff0939 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Sun, 26 Oct 2014 16:40:02 -0700 Subject: [PATCH 03/16] added folding for references to enum members in enum member initializers, added tests --- src/compiler/checker.ts | 37 ++- src/harness/harness.ts | 7 +- .../reference/constantsInEnumMembers.js | 153 +++++++++ .../reference/constantsInEnumMembers.types | 306 ++++++++++++++++++ .../cases/compiler/constantsInEnumMembers.ts | 78 +++++ 5 files changed, 566 insertions(+), 15 deletions(-) create mode 100644 tests/baselines/reference/constantsInEnumMembers.js create mode 100644 tests/baselines/reference/constantsInEnumMembers.types create mode 100644 tests/cases/compiler/constantsInEnumMembers.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 435787405a130..91edaf7c920a0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7473,18 +7473,31 @@ module ts { case SyntaxKind.PropertyAccess: if (!program.getCompilerOptions().propagateEnumConstants) return undefined; - var refSymbol = - e.kind === SyntaxKind.Identifier - ? resolveName(member, (e).text, SymbolFlags.EnumMember, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined) - : resolveEntityName(member, e, SymbolFlags.EnumMember, /*suppressErrors*/ true); - - if (!refSymbol) return undefined; - var refDecl = refSymbol.valueDeclaration; - // self references are not permitted - // non-qualified names are permitted only to members defined in the same enum - if (member === refDecl || (e.kind === SyntaxKind.Identifier && refDecl.parent !== member.parent)) return undefined; - // enumMemberValue might be undefined if corresponding enum value was not yet computed and it is ok to return undefined in this case - return getNodeLinks(refDecl).enumMemberValue; + 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 = (e).text; + } + else { + // left part in PropertyAccess should be resolved to the symbol of enum that declared 'member' + enumSymbol = resolveEntityName(member, (e).left, SymbolFlags.Enum, /*suppressErrors*/ true); + + if (enumSymbol !== getSymbolOfNode(member.parent)) return undefined; + propertyName = ((e).right).text; + } + + var propertySymbol = enumSymbol.exports[propertyName]; + if (!propertyName || !(propertySymbol.flags & SymbolFlags.EnumMember)) return undefined; + var propertyDecl = 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 getNodeLinks(propertyDecl).enumMemberValue; } } } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 60c5da5a1a68a..f537541b5b370 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -749,6 +749,9 @@ module Harness { case 'usecasesensitivefilenames': useCaseSensitiveFileNames = setting.value === 'true'; break; + case 'propagateenumconstants': + options.propagateEnumConstants = setting.value === 'true'; + break; case 'mapsourcefiles': case 'maproot': @@ -757,7 +760,6 @@ module Harness { case 'codepage': case 'createFileLog': case 'filename': - case 'propagateenumconstants': case 'removecomments': case 'watch': case 'allowautomaticsemicoloninsertion': @@ -772,7 +774,6 @@ module Harness { case 'errortruncation': options.noErrorTruncation = setting.value === 'false'; break; - default: throw new Error('Unsupported compiler setting ' + setting.flag); } @@ -1147,7 +1148,7 @@ module Harness { var optionRegex = /^[\/]{2}\s*@(\w+)\s*:\s*(\S*)/gm; // multiple matches on multiple lines // List of allowed metadata names - var fileMetadataNames = ["filename", "comments", "declaration", "module", "nolib", "sourcemap", "target", "out", "outdir", "noimplicitany", "noresolve", "newline", "newlines", "emitbom", "errortruncation", "usecasesensitivefilenames"]; + var fileMetadataNames = ["filename", "comments", "declaration", "module", "nolib", "sourcemap", "target", "out", "outdir", "noimplicitany", "noresolve", "newline", "newlines", "emitbom", "errortruncation", "usecasesensitivefilenames", "propagateenumconstants"]; function extractCompilerSettings(content: string): CompilerSetting[] { diff --git a/tests/baselines/reference/constantsInEnumMembers.js b/tests/baselines/reference/constantsInEnumMembers.js new file mode 100644 index 0000000000000..a6fc5b2ca69d7 --- /dev/null +++ b/tests/baselines/reference/constantsInEnumMembers.js @@ -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, + + // 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; + } +} diff --git a/tests/baselines/reference/constantsInEnumMembers.types b/tests/baselines/reference/constantsInEnumMembers.types new file mode 100644 index 0000000000000..c44de37649b33 --- /dev/null +++ b/tests/baselines/reference/constantsInEnumMembers.types @@ -0,0 +1,306 @@ +=== tests/cases/compiler/constantsInEnumMembers.ts === + +enum Enum1 { +>Enum1 : Enum1 + + A0 = 100, +>A0 : Enum1 +} + +enum Enum1 { +>Enum1 : Enum1 + + // correct cases + A, +>A : Enum1 + + B, +>B : Enum1 + + C = 10, +>C : Enum1 + + D = A + B, +>D : Enum1 +>A + B : number +>A : Enum1 +>B : Enum1 + + E = A + 1, +>E : Enum1 +>A + 1 : number +>A : Enum1 + + F = 1 + A, +>F : Enum1 +>1 + A : number +>A : Enum1 + + G = 1 + 1, +>G : Enum1 +>1 + 1 : number + + H = A - B, +>H : Enum1 +>A - B : number +>A : Enum1 +>B : Enum1 + + I = A - 1, +>I : Enum1 +>A - 1 : number +>A : Enum1 + + J = 1 - A, +>J : Enum1 +>1 - A : number +>A : Enum1 + + K = 1 - 1, +>K : Enum1 +>1 - 1 : number + + L = ~D, +>L : Enum1 +>~D : number +>D : Enum1 + + M = E << B, +>M : Enum1 +>E << B : number +>E : Enum1 +>B : Enum1 + + N = E << 1, +>N : Enum1 +>E << 1 : number +>E : Enum1 + + O = E >> B, +>O : Enum1 +>E >> B : number +>E : Enum1 +>B : Enum1 + + P = E >> 1, +>P : Enum1 +>E >> 1 : number +>E : Enum1 + + Q = -D, +>Q : Enum1 +>-D : number +>D : Enum1 + + R = C & 5, +>R : Enum1 +>C & 5 : number +>C : Enum1 + + S = 5 & C, +>S : Enum1 +>5 & C : number +>C : Enum1 + + T = C | D, +>T : Enum1 +>C | D : number +>C : Enum1 +>D : Enum1 + + U = C | 1, +>U : Enum1 +>C | 1 : number +>C : Enum1 + + V = 10 | D, +>V : Enum1 +>10 | D : number +>D : Enum1 + + W = Enum1.V, +>W : Enum1 +>Enum1.V : Enum1 +>Enum1 : typeof Enum1 +>V : Enum1 + + // correct cases: reference to the enum member from different enum declaration + W1 = A0, +>W1 : Enum1 +>A0 : Enum1 + + W2 = Enum1.A0, +>W2 : Enum1 +>Enum1.A0 : Enum1 +>Enum1 : typeof Enum1 +>A0 : Enum1 + + // illegal case + // forward reference to the element of the same enum + X = Y, +>X : Enum1 +>Y : Enum1 + + // forward reference to the element of the same enum + Y = Enum1.Z, +>Y : Enum1 +>Enum1.Z : Enum1 +>Enum1 : typeof Enum1 +>Z : Enum1 + + Z = 100, +>Z : Enum1 +} + + +function foo(x: Enum1) { +>foo : (x: Enum1) => void +>x : Enum1 +>Enum1 : Enum1 + + switch (x) { +>x : Enum1 + + case Enum1.A: +>Enum1.A : Enum1 +>Enum1 : typeof Enum1 +>A : Enum1 + + case Enum1.B: +>Enum1.B : Enum1 +>Enum1 : typeof Enum1 +>B : Enum1 + + case Enum1.C: +>Enum1.C : Enum1 +>Enum1 : typeof Enum1 +>C : Enum1 + + case Enum1.D: +>Enum1.D : Enum1 +>Enum1 : typeof Enum1 +>D : Enum1 + + case Enum1.E: +>Enum1.E : Enum1 +>Enum1 : typeof Enum1 +>E : Enum1 + + case Enum1.F: +>Enum1.F : Enum1 +>Enum1 : typeof Enum1 +>F : Enum1 + + case Enum1.G: +>Enum1.G : Enum1 +>Enum1 : typeof Enum1 +>G : Enum1 + + case Enum1.H: +>Enum1.H : Enum1 +>Enum1 : typeof Enum1 +>H : Enum1 + + case Enum1.I: +>Enum1.I : Enum1 +>Enum1 : typeof Enum1 +>I : Enum1 + + case Enum1.J: +>Enum1.J : Enum1 +>Enum1 : typeof Enum1 +>J : Enum1 + + case Enum1.K: +>Enum1.K : Enum1 +>Enum1 : typeof Enum1 +>K : Enum1 + + case Enum1.L: +>Enum1.L : Enum1 +>Enum1 : typeof Enum1 +>L : Enum1 + + case Enum1.M: +>Enum1.M : Enum1 +>Enum1 : typeof Enum1 +>M : Enum1 + + case Enum1.N: +>Enum1.N : Enum1 +>Enum1 : typeof Enum1 +>N : Enum1 + + case Enum1.O: +>Enum1.O : Enum1 +>Enum1 : typeof Enum1 +>O : Enum1 + + case Enum1.P: +>Enum1.P : Enum1 +>Enum1 : typeof Enum1 +>P : Enum1 + + case Enum1.Q: +>Enum1.Q : Enum1 +>Enum1 : typeof Enum1 +>Q : Enum1 + + case Enum1.R: +>Enum1.R : Enum1 +>Enum1 : typeof Enum1 +>R : Enum1 + + case Enum1.S: +>Enum1.S : Enum1 +>Enum1 : typeof Enum1 +>S : Enum1 + + case Enum1.T: +>Enum1.T : Enum1 +>Enum1 : typeof Enum1 +>T : Enum1 + + case Enum1.U: +>Enum1.U : Enum1 +>Enum1 : typeof Enum1 +>U : Enum1 + + case Enum1.V: +>Enum1.V : Enum1 +>Enum1 : typeof Enum1 +>V : Enum1 + + case Enum1.W: +>Enum1.W : Enum1 +>Enum1 : typeof Enum1 +>W : Enum1 + + case Enum1.W1: +>Enum1.W1 : Enum1 +>Enum1 : typeof Enum1 +>W1 : Enum1 + + case Enum1.W2: +>Enum1.W2 : Enum1 +>Enum1 : typeof Enum1 +>W2 : Enum1 + + case Enum1.X: +>Enum1.X : Enum1 +>Enum1 : typeof Enum1 +>X : Enum1 + + case Enum1.Y: +>Enum1.Y : Enum1 +>Enum1 : typeof Enum1 +>Y : Enum1 + + case Enum1.Z: +>Enum1.Z : Enum1 +>Enum1 : typeof Enum1 +>Z : Enum1 + + break; + } +} diff --git a/tests/cases/compiler/constantsInEnumMembers.ts b/tests/cases/compiler/constantsInEnumMembers.ts new file mode 100644 index 0000000000000..1a84cd2fb89e3 --- /dev/null +++ b/tests/cases/compiler/constantsInEnumMembers.ts @@ -0,0 +1,78 @@ +// @propagateEnumConstants: true + +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, + + // 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; + } +} \ No newline at end of file From 365587f6bf650c9812380c84da14b080c4a20be8 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Sun, 26 Oct 2014 23:42:20 -0700 Subject: [PATCH 04/16] addressed CR feedback, added support for indexed access --- src/compiler/checker.ts | 119 +++++++++------ .../reference/constantsInEnumMembers.js | 79 +++++++++- .../reference/constantsInEnumMembers.types | 136 ++++++++++++++++++ .../cases/compiler/constantsInEnumMembers.ts | 36 ++++- 4 files changed, 327 insertions(+), 43 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 91edaf7c920a0..014fba973886e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -306,6 +306,22 @@ module ts { // return undefined if we can't find a symbol. } + /** Returns true if node1 is defined before node 2**/ + function isDefinedBefore(node1: Node, node2: Node): boolean { + var file1 = getSourceFileOfNode(node1); + var file2 = getSourceFileOfNode(node2); + if (file1 === file2) { + return node1.pos <= node2.pos; + } + + if (!compilerOptions.out) { + return true; + } + + var sourceFiles = program.getSourceFiles(); + return sourceFiles.indexOf(file1) <= sourceFiles.indexOf(file2); + } + function resolveName(location: Node, name: string, meaning: SymbolFlags, nameNotFoundMessage: DiagnosticMessage, nameArg: string): Symbol { var errorLocation = location; var result: Symbol; @@ -330,18 +346,8 @@ module ts { // Block-scoped variables can not be used before their definition var declaration = forEach(s.declarations, d => d.flags & NodeFlags.BlockScoped ? d : undefined); Debug.assert(declaration, "Block-scoped variable declaration is undefined"); - var declarationSourceFile = getSourceFileOfNode(declaration); - var referenceSourceFile = getSourceFileOfNode(errorLocation); - if (declarationSourceFile === referenceSourceFile) { - if (declaration.pos > errorLocation.pos) { - error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, identifierToString(declaration.name)); - } - } - else if (compilerOptions.out) { - var sourceFiles = program.getSourceFiles(); - if (sourceFiles.indexOf(referenceSourceFile) < sourceFiles.indexOf(declarationSourceFile)) { - error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, identifierToString(declaration.name)); - } + if (!isDefinedBefore(declaration, errorLocation)) { + error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, identifierToString(declaration.name)); } } return s; @@ -489,12 +495,10 @@ module ts { } // Resolves a qualified name and any involved import aliases - function resolveEntityName(location: Node, name: EntityName, meaning: SymbolFlags, suppressErrors?: boolean): Symbol { + function resolveEntityName(location: Node, name: EntityName, meaning: SymbolFlags): Symbol { if (name.kind === SyntaxKind.Identifier) { // TODO: Investigate error recovery for symbols not found - var nameNotFoundMessage = !suppressErrors && Diagnostics.Cannot_find_name_0; - var nameArg = !suppressErrors && identifierToString(name); - var symbol = resolveName(location, (name).text, meaning, nameNotFoundMessage, nameArg); + var symbol = resolveName(location, (name).text, meaning, Diagnostics.Cannot_find_name_0, identifierToString(name)); if (!symbol) { return; } @@ -504,10 +508,8 @@ module ts { if (!namespace || namespace === unknownSymbol || (name).right.kind === SyntaxKind.Missing) return; var symbol = getSymbol(namespace.exports, (name).right.text, meaning); if (!symbol) { - if (!suppressErrors) { - error(location, Diagnostics.Module_0_has_no_exported_member_1, getFullyQualifiedName(namespace), - identifierToString((name).right)); - } + error(location, Diagnostics.Module_0_has_no_exported_member_1, getFullyQualifiedName(namespace), + identifierToString((name).right)); return; } } @@ -7410,13 +7412,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 = getConstantValueForEnumMemberInitializer(member, initializer); + autoValue = getConstantValueForEnumMemberInitializer(initializer); 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. @@ -7437,66 +7439,101 @@ module ts { nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; } - function getConstantValueForEnumMemberInitializer(member: EnumMember, initializer: Expression): number { + function getConstantValueForEnumMemberInitializer(initializer: Expression): number { return evalConstant(initializer); function evalConstant(e: Node): number { switch (e.kind) { case SyntaxKind.PrefixOperator: var value = evalConstant((e).operand); - if (value === undefined) return undefined; + if (value === undefined) { + return undefined; + } switch ((e).operator) { case SyntaxKind.PlusToken: return value; case SyntaxKind.MinusToken: return -value; - case SyntaxKind.TildeToken: return ~value; + case SyntaxKind.TildeToken: return compilerOptions.propagateEnumConstants ? ~value : undefined; } return undefined; case SyntaxKind.BinaryExpression: - if (!program.getCompilerOptions().propagateEnumConstants) return undefined; + if (!compilerOptions.propagateEnumConstants) { + return undefined; + } var left = evalConstant((e).left); - if (left === undefined) return undefined; + if (left === undefined) { + return undefined; + } var right = evalConstant((e).right); - if (right === undefined) return undefined; + if (right === undefined) { + return undefined; + } switch ((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; + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right; case SyntaxKind.LessThanLessThanToken: return left << right; } return undefined; case SyntaxKind.NumericLiteral: return +(e).text; case SyntaxKind.Identifier: + case SyntaxKind.IndexedAccess: case SyntaxKind.PropertyAccess: - if (!program.getCompilerOptions().propagateEnumConstants) return undefined; + if (!compilerOptions.propagateEnumConstants) { + return undefined; + } - var enumSymbol: Symbol; + var member = initializer.parent; + var currentType = getTypeOfSymbol(getSymbolOfNode(member.parent)); + var enumType: Type; 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); + enumType = currentType; propertyName = (e).text; } + else if (e.kind === SyntaxKind.IndexedAccess) { + if ((e).index.kind !== SyntaxKind.StringLiteral) { + return undefined; + } + var enumType = getTypeOfNode((e).object); + if (enumType !== currentType) { + return undefined; + } + propertyName = ((e).index).text; + } else { // left part in PropertyAccess should be resolved to the symbol of enum that declared 'member' - enumSymbol = resolveEntityName(member, (e).left, SymbolFlags.Enum, /*suppressErrors*/ true); - - if (enumSymbol !== getSymbolOfNode(member.parent)) return undefined; - propertyName = ((e).right).text; + var enumType = getTypeOfNode((e).left); + if (enumType !== currentType) { + return undefined; + } + propertyName = (e).right.text; } - var propertySymbol = enumSymbol.exports[propertyName]; - if (!propertyName || !(propertySymbol.flags & SymbolFlags.EnumMember)) return undefined; - var propertyDecl = propertySymbol.valueDeclaration; + if (propertyName === undefined) { + return undefined; + } + var property = getPropertyOfObjectType(enumType, propertyName); + if (!(property.flags & SymbolFlags.EnumMember)) { + return undefined; + } + var propertyDecl = property.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) + if (member === propertyDecl) { + return undefined; + } + + // illegal case: forward reference + if (!isDefinedBefore(propertyDecl, member)) { + return undefined; + } return getNodeLinks(propertyDecl).enumMemberValue; } } diff --git a/tests/baselines/reference/constantsInEnumMembers.js b/tests/baselines/reference/constantsInEnumMembers.js index a6fc5b2ca69d7..c4bd95b46d523 100644 --- a/tests/baselines/reference/constantsInEnumMembers.js +++ b/tests/baselines/reference/constantsInEnumMembers.js @@ -33,16 +33,39 @@ enum Enum1 { // 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, // forward reference to the element of the same enum Y = Enum1.Z, + Y1 = Enum1["Z"], Z = 100, } +module A { + export module B { + export module C { + export enum E { + V1 = 1, + V2 = A.B.C.E.V1 + 100 + } + } + } +} + +module A { + export module B { + export module C { + export enum E { + V3 = A.B.C.E["V2"] + 200, + } + } + } +} + function foo(x: Enum1) { switch (x) { case Enum1.A: @@ -70,11 +93,22 @@ function foo(x: Enum1) { 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; + } } //// [constantsInEnumMembers.js] @@ -111,13 +145,43 @@ var Enum1; // correct cases: reference to the enum member from different enum declaration Enum1[Enum1["W1"] = A0] = "W1"; Enum1[Enum1["W2"] = Enum1.A0] = "W2"; + Enum1[Enum1["W3"] = Enum1["A0"]] = "W3"; + Enum1[Enum1["W4"] = Enum1["W"]] = "W4"; // 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["Y1"] = Enum1["Z"]] = "Y1"; Enum1[Enum1["Z"] = 100] = "Z"; })(Enum1 || (Enum1 = {})); +var A; +(function (A) { + var B; + (function (B) { + var C; + (function (C) { + (function (E) { + E[E["V1"] = 1] = "V1"; + E[E["V2"] = A.B.C.E.V1 + 100] = "V2"; + })(C.E || (C.E = {})); + var E = C.E; + })(C = B.C || (B.C = {})); + })(B = A.B || (A.B = {})); +})(A || (A = {})); +var A; +(function (A) { + var B; + (function (B) { + var C; + (function (C) { + (function (E) { + E[E["V3"] = A.B.C.E["V2"] + 200] = "V3"; + })(C.E || (C.E = {})); + var E = C.E; + })(C = B.C || (B.C = {})); + })(B = A.B || (A.B = {})); +})(A || (A = {})); function foo(x) { switch (x) { case 0 /* A */: @@ -145,9 +209,22 @@ function foo(x) { case 11 /* W */: case 100 /* W1 */: case 100 /* W2 */: + case 100 /* W3 */: + case 11 /* W4 */: case Enum1.X: case Enum1.Y: + case Enum1.Y1: case 100 /* Z */: break; } } +function bar(e) { + switch (e) { + case 1 /* V1 */: + return 1; + case 101 /* V2 */: + return 1; + case 301 /* V3 */: + return 1; + } +} diff --git a/tests/baselines/reference/constantsInEnumMembers.types b/tests/baselines/reference/constantsInEnumMembers.types index c44de37649b33..52a1070ebe426 100644 --- a/tests/baselines/reference/constantsInEnumMembers.types +++ b/tests/baselines/reference/constantsInEnumMembers.types @@ -135,6 +135,16 @@ enum Enum1 { >Enum1 : typeof Enum1 >A0 : Enum1 + W3 = Enum1["A0"], +>W3 : Enum1 +>Enum1["A0"] : Enum1 +>Enum1 : typeof Enum1 + + W4 = Enum1["W"], +>W4 : Enum1 +>Enum1["W"] : Enum1 +>Enum1 : typeof Enum1 + // illegal case // forward reference to the element of the same enum X = Y, @@ -148,11 +158,76 @@ enum Enum1 { >Enum1 : typeof Enum1 >Z : Enum1 + Y1 = Enum1["Z"], +>Y1 : Enum1 +>Enum1["Z"] : Enum1 +>Enum1 : typeof Enum1 + Z = 100, >Z : Enum1 } +module A { +>A : typeof A + + export module B { +>B : typeof B + + export module C { +>C : typeof C + + export enum E { +>E : E + + V1 = 1, +>V1 : E + + V2 = A.B.C.E.V1 + 100 +>V2 : E +>A.B.C.E.V1 + 100 : number +>A.B.C.E.V1 : E +>A.B.C.E : typeof E +>A.B.C : typeof C +>A.B : typeof B +>A : typeof A +>B : typeof B +>C : typeof C +>E : typeof E +>V1 : E + } + } + } +} + +module A { +>A : typeof A + + export module B { +>B : typeof B + + export module C { +>C : typeof C + + export enum E { +>E : E + + V3 = A.B.C.E["V2"] + 200, +>V3 : E +>A.B.C.E["V2"] + 200 : number +>A.B.C.E["V2"] : E +>A.B.C.E : typeof E +>A.B.C : typeof C +>A.B : typeof B +>A : typeof A +>B : typeof B +>C : typeof C +>E : typeof E + } + } + } +} + function foo(x: Enum1) { >foo : (x: Enum1) => void >x : Enum1 @@ -286,6 +361,16 @@ function foo(x: Enum1) { >Enum1 : typeof Enum1 >W2 : Enum1 + case Enum1.W3: +>Enum1.W3 : Enum1 +>Enum1 : typeof Enum1 +>W3 : Enum1 + + case Enum1.W4: +>Enum1.W4 : Enum1 +>Enum1 : typeof Enum1 +>W4 : Enum1 + case Enum1.X: >Enum1.X : Enum1 >Enum1 : typeof Enum1 @@ -296,6 +381,11 @@ function foo(x: Enum1) { >Enum1 : typeof Enum1 >Y : Enum1 + case Enum1.Y1: +>Enum1.Y1 : Enum1 +>Enum1 : typeof Enum1 +>Y1 : Enum1 + case Enum1.Z: >Enum1.Z : Enum1 >Enum1 : typeof Enum1 @@ -304,3 +394,49 @@ function foo(x: Enum1) { break; } } + +function bar(e: A.B.C.E): number { +>bar : (e: A.B.C.E) => number +>e : A.B.C.E +>A : unknown +>B : unknown +>C : unknown +>E : A.B.C.E + + switch (e) { +>e : A.B.C.E + + case A.B.C.E.V1: return 1; +>A.B.C.E.V1 : A.B.C.E +>A.B.C.E : typeof A.B.C.E +>A.B.C : typeof A.B.C +>A.B : typeof A.B +>A : typeof A +>B : typeof A.B +>C : typeof A.B.C +>E : typeof A.B.C.E +>V1 : A.B.C.E + + case A.B.C.E.V2: return 1; +>A.B.C.E.V2 : A.B.C.E +>A.B.C.E : typeof A.B.C.E +>A.B.C : typeof A.B.C +>A.B : typeof A.B +>A : typeof A +>B : typeof A.B +>C : typeof A.B.C +>E : typeof A.B.C.E +>V2 : A.B.C.E + + case A.B.C.E.V3: return 1; +>A.B.C.E.V3 : A.B.C.E +>A.B.C.E : typeof A.B.C.E +>A.B.C : typeof A.B.C +>A.B : typeof A.B +>A : typeof A +>B : typeof A.B +>C : typeof A.B.C +>E : typeof A.B.C.E +>V3 : A.B.C.E + } +} diff --git a/tests/cases/compiler/constantsInEnumMembers.ts b/tests/cases/compiler/constantsInEnumMembers.ts index 1a84cd2fb89e3..8f3e5bd39e2ec 100644 --- a/tests/cases/compiler/constantsInEnumMembers.ts +++ b/tests/cases/compiler/constantsInEnumMembers.ts @@ -33,16 +33,39 @@ enum Enum1 { // 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, // forward reference to the element of the same enum Y = Enum1.Z, + Y1 = Enum1["Z"], Z = 100, } +module A { + export module B { + export module C { + export enum E { + V1 = 1, + V2 = A.B.C.E.V1 + 100 + } + } + } +} + +module A { + export module B { + export module C { + export enum E { + V3 = A.B.C.E["V2"] + 200, + } + } + } +} + function foo(x: Enum1) { switch (x) { case Enum1.A: @@ -70,9 +93,20 @@ function foo(x: Enum1) { 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; + } } \ No newline at end of file From cb472eb52c791284e307ba46df271ac91ebdb002 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 27 Oct 2014 00:17:22 -0700 Subject: [PATCH 05/16] move code around to consolidate checks in one place --- src/compiler/checker.ts | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 014fba973886e..41dfd35b8ca62 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7494,27 +7494,25 @@ module ts { 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 + // instead pick current enum type and later try to fetch member from the type enumType = currentType; propertyName = (e).text; } - else if (e.kind === SyntaxKind.IndexedAccess) { - if ((e).index.kind !== SyntaxKind.StringLiteral) { - return undefined; + else { + if (e.kind === SyntaxKind.IndexedAccess) { + if ((e).index.kind !== SyntaxKind.StringLiteral) { + return undefined; + } + var enumType = getTypeOfNode((e).object); + propertyName = ((e).index).text; } - var enumType = getTypeOfNode((e).object); - if (enumType !== currentType) { - return undefined; + else { + var enumType = getTypeOfNode((e).left); + propertyName = (e).right.text; } - propertyName = ((e).index).text; - } - else { - // left part in PropertyAccess should be resolved to the symbol of enum that declared 'member' - var enumType = getTypeOfNode((e).left); if (enumType !== currentType) { return undefined; } - propertyName = (e).right.text; } if (propertyName === undefined) { From 03cb6455735fdf19a43d7beb1d9f52fd988b9632 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 27 Oct 2014 01:24:12 -0700 Subject: [PATCH 06/16] dropped redundand type assertion, added mising check --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 41dfd35b8ca62..cf3c25893af77 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7519,10 +7519,10 @@ module ts { return undefined; } var property = getPropertyOfObjectType(enumType, propertyName); - if (!(property.flags & SymbolFlags.EnumMember)) { + if (!property || !(property.flags & SymbolFlags.EnumMember)) { return undefined; } - var propertyDecl = property.valueDeclaration; + var propertyDecl = property.valueDeclaration; // self references are illegal if (member === propertyDecl) { return undefined; From 2dd9511b0a63dc9d319f2af6ec9f81368e79765c Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 27 Oct 2014 23:56:07 -0700 Subject: [PATCH 07/16] 'const enum' iteration 0. TODO: allow and track const enums in imports, add more tests --- src/compiler/checker.ts | 64 ++- src/compiler/commandLineParser.ts | 5 - .../diagnosticInformationMap.generated.ts | 4 +- src/compiler/diagnosticMessages.json | 19 +- src/compiler/emitter.ts | 6 + src/compiler/parser.ts | 49 +- src/compiler/types.ts | 1 - src/harness/harness.ts | 5 +- .../constantsInEnumMembers.errors.txt | 122 +++++ .../reference/constantsInEnumMembers.js | 61 +-- .../reference/constantsInEnumMembers.types | 442 ------------------ .../cases/compiler/constantsInEnumMembers.ts | 10 +- 12 files changed, 242 insertions(+), 546 deletions(-) create mode 100644 tests/baselines/reference/constantsInEnumMembers.errors.txt delete mode 100644 tests/baselines/reference/constantsInEnumMembers.types diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6d4b43549b7f2..3627d64094350 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6152,6 +6152,19 @@ module ts { } } } + + if (type.flags & (TypeFlags.ObjectType | TypeFlags.Anonymous) && + type.symbol && + (type.symbol.flags & SymbolFlags.Enum) && + isConstEnumDeclaration(type.symbol.valueDeclaration)) { + // enum object type for const enums are only permitted in as 'left' in property access and 'object' in indexed access + var ok = + (node.parent.kind === SyntaxKind.PropertyAccess && (node.parent).left === node) || + (node.parent.kind === SyntaxKind.IndexedAccess && (node.parent).object === node); + if (!ok) { + error(node, Diagnostics.const_enums_can_only_be_used_in_property_access_expressions); + } + } return type; } @@ -7590,6 +7603,7 @@ module ts { var enumType = getDeclaredTypeOfSymbol(enumSymbol); var autoValue = 0; var ambient = isInAmbientContext(node); + var enumIsConst = isConstEnumDeclaration(node); forEach(node.members, member => { if(isNumericName(member.name.text)) { @@ -7597,16 +7611,21 @@ module ts { } var initializer = member.initializer; if (initializer) { - autoValue = getConstantValueForEnumMemberInitializer(initializer); - 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. - // Also, we do not need to check this for ambients because there is already - // a syntax error if it is not a constant. - checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined); + autoValue = getConstantValueForEnumMemberInitializer(initializer, enumIsConst); + if (autoValue === undefined) { + if (enumIsConst) { + error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression); + } + else if (!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. + // Also, we do not need to check this for ambients because there is already + // a syntax error if it is not a constant. + checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined); + } } } - else if (ambient) { + else if (ambient && !enumIsConst) { autoValue = undefined; } @@ -7618,7 +7637,7 @@ module ts { nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; } - function getConstantValueForEnumMemberInitializer(initializer: Expression): number { + function getConstantValueForEnumMemberInitializer(initializer: Expression, enumIsConst: boolean): number { return evalConstant(initializer); function evalConstant(e: Node): number { @@ -7631,11 +7650,11 @@ module ts { switch ((e).operator) { case SyntaxKind.PlusToken: return value; case SyntaxKind.MinusToken: return -value; - case SyntaxKind.TildeToken: return compilerOptions.propagateEnumConstants ? ~value : undefined; + case SyntaxKind.TildeToken: return enumIsConst ? ~value : undefined; } return undefined; case SyntaxKind.BinaryExpression: - if (!compilerOptions.propagateEnumConstants) { + if (!enumIsConst) { return undefined; } @@ -7655,14 +7674,20 @@ module ts { case SyntaxKind.GreaterThanGreaterThanToken: return left >> right; case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right; case SyntaxKind.LessThanLessThanToken: return left << right; + case SyntaxKind.AsteriskToken: return left * right; + case SyntaxKind.SlashToken: return left / right; + case SyntaxKind.PercentToken: return left % right; + case SyntaxKind.CaretToken: return left ^ right; } return undefined; case SyntaxKind.NumericLiteral: return +(e).text; + case SyntaxKind.ParenExpression: + return enumIsConst ? evalConstant((e).expression) : undefined; case SyntaxKind.Identifier: case SyntaxKind.IndexedAccess: case SyntaxKind.PropertyAccess: - if (!compilerOptions.propagateEnumConstants) { + if (!enumIsConst) { return undefined; } @@ -7738,6 +7763,21 @@ module ts { var enumSymbol = getSymbolOfNode(node); var firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind); if (node === firstDeclaration) { + if (enumSymbol.declarations.length > 1) { + var enumIsConst = isConstEnumDeclaration(node); + // check that const is places\omitted on all enum declarations + forEach(enumSymbol.declarations, decl => { + if (decl.kind !== SyntaxKind.EnumDeclaration) { + // TODO(vladima): do we want to allow merging for const enum declarations + return; + } + + if (isConstEnumDeclaration(decl) !== enumIsConst) { + error(decl.name, Diagnostics.Enum_declarations_must_all_be_const_or_non_const); + } + }); + } + var seenEnumMissingInitialInitializer = false; forEach(enumSymbol.declarations, declaration => { // return true if we hit a violation of the rule, false otherwise diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 667102ce31571..dbc7498e9c420 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -118,11 +118,6 @@ module ts { shortName: "w", type: "boolean", description: Diagnostics.Watch_input_files, - }, - { - name: "propagateEnumConstants", - type: "boolean", - description: Diagnostics.Propagate_constant_values_in_enum_member_initializers } ]; diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index 6e6d4425d9868..a6cc64f4497f6 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -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}" }, @@ -372,7 +375,6 @@ module ts { Specify_module_code_generation_Colon_commonjs_or_amd: { code: 6016, category: DiagnosticCategory.Message, key: "Specify module code generation: 'commonjs' or 'amd'" }, Print_this_message: { code: 6017, category: DiagnosticCategory.Message, key: "Print this message." }, Print_the_compiler_s_version: { code: 6019, category: DiagnosticCategory.Message, key: "Print the compiler's version." }, - Propagate_constant_values_in_enum_member_initializers: { code: 6020, category: DiagnosticCategory.Message, key: "Propagate constant values in enum member initializers." }, Syntax_Colon_0: { code: 6023, category: DiagnosticCategory.Message, key: "Syntax: {0}" }, options: { code: 6024, category: DiagnosticCategory.Message, key: "options" }, file: { code: 6025, category: DiagnosticCategory.Message, key: "file" }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index be0dced17ddef..37cff2f273e31 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -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 @@ -1488,10 +1497,6 @@ "category": "Message", "code": 6019 }, - "Propagate constant values in enum member initializers.": { - "category": "Message", - "code": 6020 - }, "Syntax: {0}": { "category": "Message", "code": 6023 diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 1517584e4c85f..0cf81dc46eabe 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1760,6 +1760,9 @@ module ts { } function emitEnumDeclaration(node: EnumDeclaration) { + if (isConstEnumDeclaration(node)) { + return; + } emitLeadingComments(node); if (!(node.flags & NodeFlags.Export)) { emitStart(node); @@ -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(" {"); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 15383a0031b2f..b21258fb9e6dc 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -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 && (node).expression.kind === SyntaxKind.StringLiteral; } @@ -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: @@ -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: @@ -3174,6 +3183,7 @@ module ts { if (isDeclaration()) { return false; } + case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: @@ -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(); @@ -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 @@ -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); } - } - 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 = createNode(SyntaxKind.EnumDeclaration, pos); node.flags = flags; + if (enumIsConst) { + parseExpected(SyntaxKind.ConstKeyword); + } parseExpected(SyntaxKind.EnumKeyword); node.name = parseIdentifier(); if (parseExpected(SyntaxKind.OpenBraceToken)) { @@ -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; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index fca019485c710..d28e6d67e5d16 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1095,7 +1095,6 @@ module ts { target?: ScriptTarget; version?: boolean; watch?: boolean; - propagateEnumConstants?: boolean; [option: string]: any; } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index f537541b5b370..2e837c7887d06 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -749,9 +749,6 @@ module Harness { case 'usecasesensitivefilenames': useCaseSensitiveFileNames = setting.value === 'true'; break; - case 'propagateenumconstants': - options.propagateEnumConstants = setting.value === 'true'; - break; case 'mapsourcefiles': case 'maproot': @@ -1148,7 +1145,7 @@ module Harness { var optionRegex = /^[\/]{2}\s*@(\w+)\s*:\s*(\S*)/gm; // multiple matches on multiple lines // List of allowed metadata names - var fileMetadataNames = ["filename", "comments", "declaration", "module", "nolib", "sourcemap", "target", "out", "outdir", "noimplicitany", "noresolve", "newline", "newlines", "emitbom", "errortruncation", "usecasesensitivefilenames", "propagateenumconstants"]; + var fileMetadataNames = ["filename", "comments", "declaration", "module", "nolib", "sourcemap", "target", "out", "outdir", "noimplicitany", "noresolve", "newline", "newlines", "emitbom", "errortruncation", "usecasesensitivefilenames"]; function extractCompilerSettings(content: string): CompilerSetting[] { diff --git a/tests/baselines/reference/constantsInEnumMembers.errors.txt b/tests/baselines/reference/constantsInEnumMembers.errors.txt new file mode 100644 index 0000000000000..323a352ebb275 --- /dev/null +++ b/tests/baselines/reference/constantsInEnumMembers.errors.txt @@ -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; + } + } \ No newline at end of file diff --git a/tests/baselines/reference/constantsInEnumMembers.js b/tests/baselines/reference/constantsInEnumMembers.js index c4bd95b46d523..62d6f9fbf3c31 100644 --- a/tests/baselines/reference/constantsInEnumMembers.js +++ b/tests/baselines/reference/constantsInEnumMembers.js @@ -1,10 +1,9 @@ //// [constantsInEnumMembers.ts] - -enum Enum1 { +const enum Enum1 { A0 = 100, } -enum Enum1 { +const enum Enum1 { // correct cases A, B, @@ -48,7 +47,7 @@ enum Enum1 { module A { export module B { export module C { - export enum E { + export const enum E { V1 = 1, V2 = A.B.C.E.V1 + 100 } @@ -59,7 +58,7 @@ module A { module A { export module B { export module C { - export enum E { + export const enum E { V3 = A.B.C.E["V2"] + 200, } } @@ -112,60 +111,12 @@ function bar(e: A.B.C.E): number { } //// [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"; - Enum1[Enum1["W3"] = Enum1["A0"]] = "W3"; - Enum1[Enum1["W4"] = Enum1["W"]] = "W4"; - // 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["Y1"] = Enum1["Z"]] = "Y1"; - Enum1[Enum1["Z"] = 100] = "Z"; -})(Enum1 || (Enum1 = {})); var A; (function (A) { var B; (function (B) { var C; (function (C) { - (function (E) { - E[E["V1"] = 1] = "V1"; - E[E["V2"] = A.B.C.E.V1 + 100] = "V2"; - })(C.E || (C.E = {})); - var E = C.E; })(C = B.C || (B.C = {})); })(B = A.B || (A.B = {})); })(A || (A = {})); @@ -175,10 +126,6 @@ var A; (function (B) { var C; (function (C) { - (function (E) { - E[E["V3"] = A.B.C.E["V2"] + 200] = "V3"; - })(C.E || (C.E = {})); - var E = C.E; })(C = B.C || (B.C = {})); })(B = A.B || (A.B = {})); })(A || (A = {})); diff --git a/tests/baselines/reference/constantsInEnumMembers.types b/tests/baselines/reference/constantsInEnumMembers.types deleted file mode 100644 index 52a1070ebe426..0000000000000 --- a/tests/baselines/reference/constantsInEnumMembers.types +++ /dev/null @@ -1,442 +0,0 @@ -=== tests/cases/compiler/constantsInEnumMembers.ts === - -enum Enum1 { ->Enum1 : Enum1 - - A0 = 100, ->A0 : Enum1 -} - -enum Enum1 { ->Enum1 : Enum1 - - // correct cases - A, ->A : Enum1 - - B, ->B : Enum1 - - C = 10, ->C : Enum1 - - D = A + B, ->D : Enum1 ->A + B : number ->A : Enum1 ->B : Enum1 - - E = A + 1, ->E : Enum1 ->A + 1 : number ->A : Enum1 - - F = 1 + A, ->F : Enum1 ->1 + A : number ->A : Enum1 - - G = 1 + 1, ->G : Enum1 ->1 + 1 : number - - H = A - B, ->H : Enum1 ->A - B : number ->A : Enum1 ->B : Enum1 - - I = A - 1, ->I : Enum1 ->A - 1 : number ->A : Enum1 - - J = 1 - A, ->J : Enum1 ->1 - A : number ->A : Enum1 - - K = 1 - 1, ->K : Enum1 ->1 - 1 : number - - L = ~D, ->L : Enum1 ->~D : number ->D : Enum1 - - M = E << B, ->M : Enum1 ->E << B : number ->E : Enum1 ->B : Enum1 - - N = E << 1, ->N : Enum1 ->E << 1 : number ->E : Enum1 - - O = E >> B, ->O : Enum1 ->E >> B : number ->E : Enum1 ->B : Enum1 - - P = E >> 1, ->P : Enum1 ->E >> 1 : number ->E : Enum1 - - Q = -D, ->Q : Enum1 ->-D : number ->D : Enum1 - - R = C & 5, ->R : Enum1 ->C & 5 : number ->C : Enum1 - - S = 5 & C, ->S : Enum1 ->5 & C : number ->C : Enum1 - - T = C | D, ->T : Enum1 ->C | D : number ->C : Enum1 ->D : Enum1 - - U = C | 1, ->U : Enum1 ->C | 1 : number ->C : Enum1 - - V = 10 | D, ->V : Enum1 ->10 | D : number ->D : Enum1 - - W = Enum1.V, ->W : Enum1 ->Enum1.V : Enum1 ->Enum1 : typeof Enum1 ->V : Enum1 - - // correct cases: reference to the enum member from different enum declaration - W1 = A0, ->W1 : Enum1 ->A0 : Enum1 - - W2 = Enum1.A0, ->W2 : Enum1 ->Enum1.A0 : Enum1 ->Enum1 : typeof Enum1 ->A0 : Enum1 - - W3 = Enum1["A0"], ->W3 : Enum1 ->Enum1["A0"] : Enum1 ->Enum1 : typeof Enum1 - - W4 = Enum1["W"], ->W4 : Enum1 ->Enum1["W"] : Enum1 ->Enum1 : typeof Enum1 - - // illegal case - // forward reference to the element of the same enum - X = Y, ->X : Enum1 ->Y : Enum1 - - // forward reference to the element of the same enum - Y = Enum1.Z, ->Y : Enum1 ->Enum1.Z : Enum1 ->Enum1 : typeof Enum1 ->Z : Enum1 - - Y1 = Enum1["Z"], ->Y1 : Enum1 ->Enum1["Z"] : Enum1 ->Enum1 : typeof Enum1 - - Z = 100, ->Z : Enum1 -} - - -module A { ->A : typeof A - - export module B { ->B : typeof B - - export module C { ->C : typeof C - - export enum E { ->E : E - - V1 = 1, ->V1 : E - - V2 = A.B.C.E.V1 + 100 ->V2 : E ->A.B.C.E.V1 + 100 : number ->A.B.C.E.V1 : E ->A.B.C.E : typeof E ->A.B.C : typeof C ->A.B : typeof B ->A : typeof A ->B : typeof B ->C : typeof C ->E : typeof E ->V1 : E - } - } - } -} - -module A { ->A : typeof A - - export module B { ->B : typeof B - - export module C { ->C : typeof C - - export enum E { ->E : E - - V3 = A.B.C.E["V2"] + 200, ->V3 : E ->A.B.C.E["V2"] + 200 : number ->A.B.C.E["V2"] : E ->A.B.C.E : typeof E ->A.B.C : typeof C ->A.B : typeof B ->A : typeof A ->B : typeof B ->C : typeof C ->E : typeof E - } - } - } -} - -function foo(x: Enum1) { ->foo : (x: Enum1) => void ->x : Enum1 ->Enum1 : Enum1 - - switch (x) { ->x : Enum1 - - case Enum1.A: ->Enum1.A : Enum1 ->Enum1 : typeof Enum1 ->A : Enum1 - - case Enum1.B: ->Enum1.B : Enum1 ->Enum1 : typeof Enum1 ->B : Enum1 - - case Enum1.C: ->Enum1.C : Enum1 ->Enum1 : typeof Enum1 ->C : Enum1 - - case Enum1.D: ->Enum1.D : Enum1 ->Enum1 : typeof Enum1 ->D : Enum1 - - case Enum1.E: ->Enum1.E : Enum1 ->Enum1 : typeof Enum1 ->E : Enum1 - - case Enum1.F: ->Enum1.F : Enum1 ->Enum1 : typeof Enum1 ->F : Enum1 - - case Enum1.G: ->Enum1.G : Enum1 ->Enum1 : typeof Enum1 ->G : Enum1 - - case Enum1.H: ->Enum1.H : Enum1 ->Enum1 : typeof Enum1 ->H : Enum1 - - case Enum1.I: ->Enum1.I : Enum1 ->Enum1 : typeof Enum1 ->I : Enum1 - - case Enum1.J: ->Enum1.J : Enum1 ->Enum1 : typeof Enum1 ->J : Enum1 - - case Enum1.K: ->Enum1.K : Enum1 ->Enum1 : typeof Enum1 ->K : Enum1 - - case Enum1.L: ->Enum1.L : Enum1 ->Enum1 : typeof Enum1 ->L : Enum1 - - case Enum1.M: ->Enum1.M : Enum1 ->Enum1 : typeof Enum1 ->M : Enum1 - - case Enum1.N: ->Enum1.N : Enum1 ->Enum1 : typeof Enum1 ->N : Enum1 - - case Enum1.O: ->Enum1.O : Enum1 ->Enum1 : typeof Enum1 ->O : Enum1 - - case Enum1.P: ->Enum1.P : Enum1 ->Enum1 : typeof Enum1 ->P : Enum1 - - case Enum1.Q: ->Enum1.Q : Enum1 ->Enum1 : typeof Enum1 ->Q : Enum1 - - case Enum1.R: ->Enum1.R : Enum1 ->Enum1 : typeof Enum1 ->R : Enum1 - - case Enum1.S: ->Enum1.S : Enum1 ->Enum1 : typeof Enum1 ->S : Enum1 - - case Enum1.T: ->Enum1.T : Enum1 ->Enum1 : typeof Enum1 ->T : Enum1 - - case Enum1.U: ->Enum1.U : Enum1 ->Enum1 : typeof Enum1 ->U : Enum1 - - case Enum1.V: ->Enum1.V : Enum1 ->Enum1 : typeof Enum1 ->V : Enum1 - - case Enum1.W: ->Enum1.W : Enum1 ->Enum1 : typeof Enum1 ->W : Enum1 - - case Enum1.W1: ->Enum1.W1 : Enum1 ->Enum1 : typeof Enum1 ->W1 : Enum1 - - case Enum1.W2: ->Enum1.W2 : Enum1 ->Enum1 : typeof Enum1 ->W2 : Enum1 - - case Enum1.W3: ->Enum1.W3 : Enum1 ->Enum1 : typeof Enum1 ->W3 : Enum1 - - case Enum1.W4: ->Enum1.W4 : Enum1 ->Enum1 : typeof Enum1 ->W4 : Enum1 - - case Enum1.X: ->Enum1.X : Enum1 ->Enum1 : typeof Enum1 ->X : Enum1 - - case Enum1.Y: ->Enum1.Y : Enum1 ->Enum1 : typeof Enum1 ->Y : Enum1 - - case Enum1.Y1: ->Enum1.Y1 : Enum1 ->Enum1 : typeof Enum1 ->Y1 : Enum1 - - case Enum1.Z: ->Enum1.Z : Enum1 ->Enum1 : typeof Enum1 ->Z : Enum1 - - break; - } -} - -function bar(e: A.B.C.E): number { ->bar : (e: A.B.C.E) => number ->e : A.B.C.E ->A : unknown ->B : unknown ->C : unknown ->E : A.B.C.E - - switch (e) { ->e : A.B.C.E - - case A.B.C.E.V1: return 1; ->A.B.C.E.V1 : A.B.C.E ->A.B.C.E : typeof A.B.C.E ->A.B.C : typeof A.B.C ->A.B : typeof A.B ->A : typeof A ->B : typeof A.B ->C : typeof A.B.C ->E : typeof A.B.C.E ->V1 : A.B.C.E - - case A.B.C.E.V2: return 1; ->A.B.C.E.V2 : A.B.C.E ->A.B.C.E : typeof A.B.C.E ->A.B.C : typeof A.B.C ->A.B : typeof A.B ->A : typeof A ->B : typeof A.B ->C : typeof A.B.C ->E : typeof A.B.C.E ->V2 : A.B.C.E - - case A.B.C.E.V3: return 1; ->A.B.C.E.V3 : A.B.C.E ->A.B.C.E : typeof A.B.C.E ->A.B.C : typeof A.B.C ->A.B : typeof A.B ->A : typeof A ->B : typeof A.B ->C : typeof A.B.C ->E : typeof A.B.C.E ->V3 : A.B.C.E - } -} diff --git a/tests/cases/compiler/constantsInEnumMembers.ts b/tests/cases/compiler/constantsInEnumMembers.ts index 8f3e5bd39e2ec..dfaac3ffe0a66 100644 --- a/tests/cases/compiler/constantsInEnumMembers.ts +++ b/tests/cases/compiler/constantsInEnumMembers.ts @@ -1,10 +1,8 @@ -// @propagateEnumConstants: true - -enum Enum1 { +const enum Enum1 { A0 = 100, } -enum Enum1 { +const enum Enum1 { // correct cases A, B, @@ -48,7 +46,7 @@ enum Enum1 { module A { export module B { export module C { - export enum E { + export const enum E { V1 = 1, V2 = A.B.C.E.V1 + 100 } @@ -59,7 +57,7 @@ module A { module A { export module B { export module C { - export enum E { + export const enum E { V3 = A.B.C.E["V2"] + 200, } } From e949eda583090c74155a728bef898ca9406d6f61 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 29 Oct 2014 00:17:16 -0700 Subject: [PATCH 08/16] const enums, iteration 1: const enums can be used in imports, const enums can be merged only with const enums. --- src/compiler/binder.ts | 21 +- src/compiler/checker.ts | 61 ++- src/compiler/commandLineParser.ts | 4 + .../diagnosticInformationMap.generated.ts | 1 + src/compiler/diagnosticMessages.json | 6 +- src/compiler/emitter.ts | 5 +- src/compiler/types.ts | 5 +- src/services/breakpoints.ts | 6 +- src/services/services.ts | 4 +- .../reference/constEnumErrors.errors.txt | 65 +++ tests/baselines/reference/constEnumErrors.js | 47 ++ ...onstantsInEnumMembers.js => constEnums.js} | 84 ++-- tests/baselines/reference/constEnums.types | 433 ++++++++++++++++++ .../constantsInEnumMembers.errors.txt | 122 ----- tests/cases/compiler/constEnumErrors.ts | 32 ++ ...onstantsInEnumMembers.ts => constEnums.ts} | 40 +- 16 files changed, 703 insertions(+), 233 deletions(-) create mode 100644 tests/baselines/reference/constEnumErrors.errors.txt create mode 100644 tests/baselines/reference/constEnumErrors.js rename tests/baselines/reference/{constantsInEnumMembers.js => constEnums.js} (65%) create mode 100644 tests/baselines/reference/constEnums.types delete mode 100644 tests/baselines/reference/constantsInEnumMembers.errors.txt create mode 100644 tests/cases/compiler/constEnumErrors.ts rename tests/cases/compiler/{constantsInEnumMembers.ts => constEnums.ts} (72%) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index f0b2d71ccadfc..81404c933efcc 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -5,21 +5,25 @@ module ts { - export function isInstantiated(node: Node): boolean { + export function isInstantiated(node: Node, checkConstEnums: boolean): boolean { // A module is uninstantiated if it contains only // 1. interface declarations if (node.kind === SyntaxKind.InterfaceDeclaration) { return false; } - // 2. non - exported import declarations + // 2. const enum declarations don't make module instantiated + else if (checkConstEnums && node.kind === SyntaxKind.EnumDeclaration && isConstEnumDeclaration(node)) { + return false; + } + // 3. non - exported import declarations else if (node.kind === SyntaxKind.ImportDeclaration && !(node.flags & NodeFlags.Export)) { return false; } - // 3. other uninstantiated module declarations. - else if (node.kind === SyntaxKind.ModuleBlock && !forEachChild(node, isInstantiated)) { + // 4. other uninstantiated module declarations. + else if (node.kind === SyntaxKind.ModuleBlock && !forEachChild(node, n => isInstantiated(n, checkConstEnums))) { return false; } - else if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated((node).body)) { + else if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated((node).body, checkConstEnums)) { return false; } else { @@ -248,7 +252,7 @@ module ts { if (node.name.kind === SyntaxKind.StringLiteral) { bindDeclaration(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes, /*isBlockScopeContainer*/ true); } - else if (isInstantiated(node)) { + else if (isInstantiated(node, /*checkConstEnums*/ false)) { bindDeclaration(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes, /*isBlockScopeContainer*/ true); } else { @@ -364,7 +368,10 @@ module ts { bindDeclaration(node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes, /*isBlockScopeContainer*/ false); break; case SyntaxKind.EnumDeclaration: - bindDeclaration(node, SymbolFlags.Enum, SymbolFlags.EnumExcludes, /*isBlockScopeContainer*/ false); + var enumIsConst = isConstEnumDeclaration(node); + var flags = enumIsConst ? SymbolFlags.Enum | SymbolFlags.ConstEnum : SymbolFlags.Enum; + var excludes = enumIsConst ? SymbolFlags.ConstEnumExcludes : SymbolFlags.EnumExcludes; + bindDeclaration(node, flags, excludes, /*isBlockScopeContainer*/ false); break; case SyntaxKind.ModuleDeclaration: bindModuleDeclaration(node); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 27bb6548b9072..02757e458c811 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4431,8 +4431,8 @@ module ts { if (symbol.flags & SymbolFlags.Import) { // Mark the import as referenced so that we emit it in the final .js file. - // exception: identifiers that appear in type queries - getSymbolLinks(symbol).referenced = !isInTypeQuery(node); + // exception: identifiers that appear in type queries, const enums + getSymbolLinks(symbol).referenced = !isInTypeQuery(node) && !isConstEnumSymbol(resolveImport(symbol)); } checkCollisionWithCapturedSuperVariable(node, node); @@ -5086,6 +5086,10 @@ module ts { if (objectType === unknownType) return unknownType; + if (isConstEnumType(objectType) && node.index.kind !== SyntaxKind.StringLiteral) { + error(node.index, Diagnostics.Index_expression_arguments_in_const_enums_must_be_of_type_string); + } + // TypeScript 1.0 spec (April 2014): 4.10 Property Access // - If IndexExpr is a string literal or a numeric literal and ObjExpr's apparent type has a property with the name // 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 { return (type.flags & TypeFlags.Structured) !== 0; } + function isConstEnumType(type: Type) : boolean { + return type.flags & (TypeFlags.ObjectType | TypeFlags.Anonymous) && type.symbol && isConstEnumSymbol(type.symbol); + } + + function isConstEnumSymbol(symbol: Symbol): boolean { + return (symbol.flags & SymbolFlags.ConstEnum) !== 0; + } + function checkInstanceOfExpression(node: BinaryExpression, leftType: Type, rightType: Type): Type { // TypeScript 1.0 spec (April 2014): 4.15.4 // 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 { } } - if (type.flags & (TypeFlags.ObjectType | TypeFlags.Anonymous) && - type.symbol && - (type.symbol.flags & SymbolFlags.Enum) && - isConstEnumDeclaration(type.symbol.valueDeclaration)) { - // enum object type for const enums are only permitted in as 'left' in property access and 'object' in indexed access + if (isConstEnumType(type)) { + // enum object type for const enums are only permitted in: + // - 'left' in property access + // - 'object' in indexed access + // - target in rhs of import statement var ok = (node.parent.kind === SyntaxKind.PropertyAccess && (node.parent).left === node) || - (node.parent.kind === SyntaxKind.IndexedAccess && (node.parent).object === node); + (node.parent.kind === SyntaxKind.IndexedAccess && (node.parent).object === node) || + isRhsOfImportStatement(node); + if (!ok) { error(node, Diagnostics.const_enums_can_only_be_used_in_property_access_expressions); } } return type; + + function isRhsOfImportStatement(n: Node): boolean { + while (n.parent) { + if (n.parent.kind === SyntaxKind.ImportDeclaration && (n.parent).entityName === n) { + return true; + } + n = n.parent; + } + return false; + } } function checkExpressionNode(node: Expression, contextualMapper: TypeMapper): Type { @@ -6868,7 +6892,7 @@ module ts { case SyntaxKind.InterfaceDeclaration: return SymbolFlags.ExportType; case SyntaxKind.ModuleDeclaration: - return (d).name.kind === SyntaxKind.StringLiteral || isInstantiated(d) + return (d).name.kind === SyntaxKind.StringLiteral || isInstantiated(d, /*checkConstEnums*/ false) ? SymbolFlags.ExportNamespace | SymbolFlags.ExportValue : SymbolFlags.ExportNamespace; case SyntaxKind.ClassDeclaration: @@ -7105,7 +7129,7 @@ module ts { } // Uninstantiated modules shouldnt do this check - if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated(node)) { + if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated(node, /*checkConstEnums*/ true)) { return; } @@ -7707,14 +7731,9 @@ module ts { switch ((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; case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right; case SyntaxKind.LessThanLessThanToken: return left << right; - case SyntaxKind.AsteriskToken: return left * right; - case SyntaxKind.SlashToken: return left / right; - case SyntaxKind.PercentToken: return left % right; case SyntaxKind.CaretToken: return left ^ right; } return undefined; @@ -7803,13 +7822,8 @@ module ts { if (node === firstDeclaration) { if (enumSymbol.declarations.length > 1) { var enumIsConst = isConstEnumDeclaration(node); - // check that const is places\omitted on all enum declarations + // check that const is placed\omitted on all enum declarations forEach(enumSymbol.declarations, decl => { - if (decl.kind !== SyntaxKind.EnumDeclaration) { - // TODO(vladima): do we want to allow merging for const enum declarations - return; - } - if (isConstEnumDeclaration(decl) !== enumIsConst) { error(decl.name, Diagnostics.Enum_declarations_must_all_be_const_or_non_const); } @@ -8655,7 +8669,7 @@ module ts { } var symbol = getSymbolOfNode(node); var target = resolveImport(symbol); - return target !== unknownSymbol && ((target.flags & SymbolFlags.Value) !== 0); + return target !== unknownSymbol && ((target.flags & SymbolFlags.Value) !== 0) && !isConstEnumSymbol(target); } function hasSemanticErrors() { @@ -8676,7 +8690,8 @@ module ts { // As a consequence this might cause emitting extra. if (node.flags & NodeFlags.Export) { var target = resolveImport(symbol); - if (target !== unknownSymbol && target.flags & SymbolFlags.Value) { + // importing const enum does not cause import to be referenced + if (target !== unknownSymbol && target.flags & SymbolFlags.Value && !isConstEnumSymbol(target)) { return true; } } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 4ee747c0c0bb1..47520cb031771 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -118,6 +118,10 @@ module ts { shortName: "w", type: "boolean", description: Diagnostics.Watch_input_files, + }, + { + name: "preserveConstEnums", + type: "boolean" } ]; diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index 211531e2fa000..8bf05e403c897 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -353,6 +353,7 @@ module ts { Enum_declarations_must_all_be_const_or_non_const: { code: 4082, category: DiagnosticCategory.Error, key: "Enum declarations must all be const or non-const." }, 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." }, 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." }, + 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'." }, 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}" }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 713fa09798075..9b9094223ce08 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1412,7 +1412,11 @@ "'const' enums can only be used in property access expressions.": { "category": "Error", "code": 4084 - }, + }, + "Index expression arguments in 'const' enums must be of type 'string'.": { + "category": "Error", + "code": 4085 + }, "The current host does not support the '{0}' option.": { "category": "Error", "code": 5001 diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 908505025ab12..42992107eaac1 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1760,7 +1760,8 @@ module ts { } function emitEnumDeclaration(node: EnumDeclaration) { - if (isConstEnumDeclaration(node)) { + // const enums are completely erased during compilation. + if (isConstEnumDeclaration(node) && !compilerOptions.preserveConstEnums) { return; } emitLeadingComments(node); @@ -1837,7 +1838,7 @@ module ts { } function emitModuleDeclaration(node: ModuleDeclaration) { - if (!isInstantiated(node)) { + if (!isInstantiated(node, /*checkConstEnums*/ true)) { return emitPinnedOrTripleSlashComments(node); } emitLeadingComments(node); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6a51691f1ab3b..055ae8b3e7738 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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 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), 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; } diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index 3a72ee9bfa63a..319bb12d89418 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -178,7 +178,7 @@ module ts.BreakpointResolver { case SyntaxKind.ModuleDeclaration: // span on complete module if it is instantiated - if (!isInstantiated(node)) { + if (!isInstantiated(node, /*checkConstEnums*/ true)) { return undefined; } @@ -350,7 +350,7 @@ module ts.BreakpointResolver { function spanInBlock(block: Block): TypeScript.TextSpan { switch (block.parent.kind) { case SyntaxKind.ModuleDeclaration: - if (!isInstantiated(block.parent)) { + if (!isInstantiated(block.parent, /*checkConstEnums*/ true)) { return undefined; } @@ -407,7 +407,7 @@ module ts.BreakpointResolver { switch (node.parent.kind) { case SyntaxKind.ModuleBlock: // If this is not instantiated module block no bp span - if (!isInstantiated(node.parent.parent)) { + if (!isInstantiated(node.parent.parent, /*checkConstEnums*/ true)) { return undefined; } diff --git a/src/services/services.ts b/src/services/services.ts index d2e4360725da0..0b14ad90c24c0 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4549,7 +4549,7 @@ module ts { if ((node).name.kind === SyntaxKind.StringLiteral) { return SemanticMeaning.Namespace | SemanticMeaning.Value; } - else if (isInstantiated(node)) { + else if (isInstantiated(node, /*checkConstEnums*/ true)) { return SemanticMeaning.Namespace | SemanticMeaning.Value; } else { @@ -4826,7 +4826,7 @@ module ts { */ function hasValueSideModule(symbol: Symbol): boolean { return forEach(symbol.declarations, declaration => { - return declaration.kind === SyntaxKind.ModuleDeclaration && isInstantiated(declaration); + return declaration.kind === SyntaxKind.ModuleDeclaration && isInstantiated(declaration, /*checkConstEnums*/ true); }); } } diff --git a/tests/baselines/reference/constEnumErrors.errors.txt b/tests/baselines/reference/constEnumErrors.errors.txt new file mode 100644 index 0000000000000..2464180ad3448 --- /dev/null +++ b/tests/baselines/reference/constEnumErrors.errors.txt @@ -0,0 +1,65 @@ +tests/cases/compiler/constEnumErrors.ts(1,12): error TS2300: Duplicate identifier 'E'. +tests/cases/compiler/constEnumErrors.ts(5,8): error TS2300: Duplicate identifier 'E'. +tests/cases/compiler/constEnumErrors.ts(12,9): error TS4083: In 'const' enum declarations member initializer must be constant expression. +tests/cases/compiler/constEnumErrors.ts(14,9): error TS4083: In 'const' enum declarations member initializer must be constant expression. +tests/cases/compiler/constEnumErrors.ts(15,10): error TS4083: In 'const' enum declarations member initializer must be constant expression. +tests/cases/compiler/constEnumErrors.ts(22,13): error TS4085: Index expression arguments in 'const' enums must be of type 'string'. +tests/cases/compiler/constEnumErrors.ts(24,13): error TS4085: Index expression arguments in 'const' enums must be of type 'string'. +tests/cases/compiler/constEnumErrors.ts(26,9): error TS4084: 'const' enums can only be used in property access expressions. +tests/cases/compiler/constEnumErrors.ts(27,10): error TS4084: 'const' enums can only be used in property access expressions. +tests/cases/compiler/constEnumErrors.ts(32,5): error TS4084: 'const' enums can only be used in property access expressions. + + +==== tests/cases/compiler/constEnumErrors.ts (10 errors) ==== + const enum E { + ~ +!!! error TS2300: Duplicate identifier 'E'. + A + } + + module E { + ~ +!!! error TS2300: Duplicate identifier 'E'. + var x = 1; + } + + const enum E1 { + // illegal case + // forward reference to the element of the same enum + X = Y, + ~ +!!! error TS4083: In 'const' enum declarations member initializer must be constant expression. + // forward reference to the element of the same enum + Y = E1.Z, + ~~~~ +!!! error TS4083: In 'const' enum declarations member initializer must be constant expression. + Y1 = E1["Z"] + ~~~~~~~ +!!! error TS4083: In 'const' enum declarations member initializer must be constant expression. + } + + const enum E2 { + A + } + + var y0 = E2[1] + ~ +!!! error TS4085: Index expression arguments in 'const' enums must be of type 'string'. + var name = "A"; + var y1 = E2[name]; + ~~~~ +!!! error TS4085: Index expression arguments in 'const' enums must be of type 'string'. + + var x = E2; + ~~ +!!! error TS4084: 'const' enums can only be used in property access expressions. + var y = [E2]; + ~~ +!!! error TS4084: 'const' enums can only be used in property access expressions. + + function foo(t: any): void { + } + + foo(E2); + ~~ +!!! error TS4084: 'const' enums can only be used in property access expressions. \ No newline at end of file diff --git a/tests/baselines/reference/constEnumErrors.js b/tests/baselines/reference/constEnumErrors.js new file mode 100644 index 0000000000000..7a1260583df98 --- /dev/null +++ b/tests/baselines/reference/constEnumErrors.js @@ -0,0 +1,47 @@ +//// [constEnumErrors.ts] +const enum E { + A +} + +module E { + var x = 1; +} + +const enum E1 { + // illegal case + // forward reference to the element of the same enum + X = Y, + // forward reference to the element of the same enum + Y = E1.Z, + Y1 = E1["Z"] +} + +const enum E2 { + A +} + +var y0 = E2[1] +var name = "A"; +var y1 = E2[name]; + +var x = E2; +var y = [E2]; + +function foo(t: any): void { +} + +foo(E2); + +//// [constEnumErrors.js] +var E; +(function (E) { + var x = 1; +})(E || (E = {})); +var y0 = E2[1]; +var name = "A"; +var y1 = E2[name]; +var x = E2; +var y = [E2]; +function foo(t) { +} +foo(E2); diff --git a/tests/baselines/reference/constantsInEnumMembers.js b/tests/baselines/reference/constEnums.js similarity index 65% rename from tests/baselines/reference/constantsInEnumMembers.js rename to tests/baselines/reference/constEnums.js index 62d6f9fbf3c31..aa19b8e9230b3 100644 --- a/tests/baselines/reference/constantsInEnumMembers.js +++ b/tests/baselines/reference/constEnums.js @@ -1,4 +1,4 @@ -//// [constantsInEnumMembers.ts] +//// [constEnums.ts] const enum Enum1 { A0 = 100, } @@ -8,14 +8,14 @@ const enum Enum1 { 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, + D = A | B, + E = A | 1, + F = 1 | A, + G = (1 & 1), + H = ~(A | B), + I = A >>> 1, + J = 1 & A, + K = ~(1 | 5), L = ~D, M = E << B, N = E << 1, @@ -34,13 +34,6 @@ const enum Enum1 { W2 = Enum1.A0, W3 = Enum1["A0"], W4 = Enum1["W"], - // 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, - Y1 = Enum1["Z"], - Z = 100, } @@ -49,7 +42,7 @@ module A { export module C { export const enum E { V1 = 1, - V2 = A.B.C.E.V1 + 100 + V2 = A.B.C.E.V1 | 100 } } } @@ -59,12 +52,21 @@ module A { export module B { export module C { export const enum E { - V3 = A.B.C.E["V2"] + 200, + V3 = A.B.C.E["V2"] & 200, } } } } +import I = A.B.C.E; + +function foo0(e: I): void { + if (e === I.V1) { + } + else if (e === I.V2) { + } +} + function foo(x: Enum1) { switch (x) { case Enum1.A: @@ -94,10 +96,6 @@ function foo(x: Enum1) { case Enum1.W2: case Enum1.W3: case Enum1.W4: - case Enum1.X: - case Enum1.Y: - case Enum1.Y1: - case Enum1.Z: break; } } @@ -110,25 +108,13 @@ function bar(e: A.B.C.E): number { } } -//// [constantsInEnumMembers.js] -var A; -(function (A) { - var B; - (function (B) { - var C; - (function (C) { - })(C = B.C || (B.C = {})); - })(B = A.B || (A.B = {})); -})(A || (A = {})); -var A; -(function (A) { - var B; - (function (B) { - var C; - (function (C) { - })(C = B.C || (B.C = {})); - })(B = A.B || (A.B = {})); -})(A || (A = {})); +//// [constEnums.js] +function foo0(e) { + if (e === 1 /* V1 */) { + } + else if (e === 101 /* V2 */) { + } +} function foo(x) { switch (x) { case 0 /* A */: @@ -137,11 +123,11 @@ function foo(x) { 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 1 /* G */: + case -2 /* H */: + case 0 /* I */: + case 0 /* J */: + case -6 /* K */: case -2 /* L */: case 2 /* M */: case 2 /* N */: @@ -158,10 +144,6 @@ function foo(x) { case 100 /* W2 */: case 100 /* W3 */: case 11 /* W4 */: - case Enum1.X: - case Enum1.Y: - case Enum1.Y1: - case 100 /* Z */: break; } } @@ -171,7 +153,7 @@ function bar(e) { return 1; case 101 /* V2 */: return 1; - case 301 /* V3 */: + case 64 /* V3 */: return 1; } } diff --git a/tests/baselines/reference/constEnums.types b/tests/baselines/reference/constEnums.types new file mode 100644 index 0000000000000..3ce0eeac9e306 --- /dev/null +++ b/tests/baselines/reference/constEnums.types @@ -0,0 +1,433 @@ +=== tests/cases/compiler/constEnums.ts === +const enum Enum1 { +>Enum1 : Enum1 + + A0 = 100, +>A0 : Enum1 +} + +const enum Enum1 { +>Enum1 : Enum1 + + // correct cases + A, +>A : Enum1 + + B, +>B : Enum1 + + C = 10, +>C : Enum1 + + D = A | B, +>D : Enum1 +>A | B : number +>A : Enum1 +>B : Enum1 + + E = A | 1, +>E : Enum1 +>A | 1 : number +>A : Enum1 + + F = 1 | A, +>F : Enum1 +>1 | A : number +>A : Enum1 + + G = (1 & 1), +>G : Enum1 +>(1 & 1) : number +>1 & 1 : number + + H = ~(A | B), +>H : Enum1 +>~(A | B) : number +>(A | B) : number +>A | B : number +>A : Enum1 +>B : Enum1 + + I = A >>> 1, +>I : Enum1 +>A >>> 1 : number +>A : Enum1 + + J = 1 & A, +>J : Enum1 +>1 & A : number +>A : Enum1 + + K = ~(1 | 5), +>K : Enum1 +>~(1 | 5) : number +>(1 | 5) : number +>1 | 5 : number + + L = ~D, +>L : Enum1 +>~D : number +>D : Enum1 + + M = E << B, +>M : Enum1 +>E << B : number +>E : Enum1 +>B : Enum1 + + N = E << 1, +>N : Enum1 +>E << 1 : number +>E : Enum1 + + O = E >> B, +>O : Enum1 +>E >> B : number +>E : Enum1 +>B : Enum1 + + P = E >> 1, +>P : Enum1 +>E >> 1 : number +>E : Enum1 + + Q = -D, +>Q : Enum1 +>-D : number +>D : Enum1 + + R = C & 5, +>R : Enum1 +>C & 5 : number +>C : Enum1 + + S = 5 & C, +>S : Enum1 +>5 & C : number +>C : Enum1 + + T = C | D, +>T : Enum1 +>C | D : number +>C : Enum1 +>D : Enum1 + + U = C | 1, +>U : Enum1 +>C | 1 : number +>C : Enum1 + + V = 10 | D, +>V : Enum1 +>10 | D : number +>D : Enum1 + + W = Enum1.V, +>W : Enum1 +>Enum1.V : Enum1 +>Enum1 : typeof Enum1 +>V : Enum1 + + // correct cases: reference to the enum member from different enum declaration + W1 = A0, +>W1 : Enum1 +>A0 : Enum1 + + W2 = Enum1.A0, +>W2 : Enum1 +>Enum1.A0 : Enum1 +>Enum1 : typeof Enum1 +>A0 : Enum1 + + W3 = Enum1["A0"], +>W3 : Enum1 +>Enum1["A0"] : Enum1 +>Enum1 : typeof Enum1 + + W4 = Enum1["W"], +>W4 : Enum1 +>Enum1["W"] : Enum1 +>Enum1 : typeof Enum1 +} + + +module A { +>A : typeof A + + export module B { +>B : typeof B + + export module C { +>C : typeof C + + export const enum E { +>E : E + + V1 = 1, +>V1 : E + + V2 = A.B.C.E.V1 | 100 +>V2 : E +>A.B.C.E.V1 | 100 : number +>A.B.C.E.V1 : E +>A.B.C.E : typeof E +>A.B.C : typeof C +>A.B : typeof B +>A : typeof A +>B : typeof B +>C : typeof C +>E : typeof E +>V1 : E + } + } + } +} + +module A { +>A : typeof A + + export module B { +>B : typeof B + + export module C { +>C : typeof C + + export const enum E { +>E : E + + V3 = A.B.C.E["V2"] & 200, +>V3 : E +>A.B.C.E["V2"] & 200 : number +>A.B.C.E["V2"] : E +>A.B.C.E : typeof E +>A.B.C : typeof C +>A.B : typeof B +>A : typeof A +>B : typeof B +>C : typeof C +>E : typeof E + } + } + } +} + +import I = A.B.C.E; +>I : typeof I +>A : typeof A +>B : typeof A.B +>C : typeof A.B.C +>E : I + +function foo0(e: I): void { +>foo0 : (e: I) => void +>e : I +>I : I + + if (e === I.V1) { +>e === I.V1 : boolean +>e : I +>I.V1 : I +>I : typeof I +>V1 : I + } + else if (e === I.V2) { +>e === I.V2 : boolean +>e : I +>I.V2 : I +>I : typeof I +>V2 : I + } +} + +function foo(x: Enum1) { +>foo : (x: Enum1) => void +>x : Enum1 +>Enum1 : Enum1 + + switch (x) { +>x : Enum1 + + case Enum1.A: +>Enum1.A : Enum1 +>Enum1 : typeof Enum1 +>A : Enum1 + + case Enum1.B: +>Enum1.B : Enum1 +>Enum1 : typeof Enum1 +>B : Enum1 + + case Enum1.C: +>Enum1.C : Enum1 +>Enum1 : typeof Enum1 +>C : Enum1 + + case Enum1.D: +>Enum1.D : Enum1 +>Enum1 : typeof Enum1 +>D : Enum1 + + case Enum1.E: +>Enum1.E : Enum1 +>Enum1 : typeof Enum1 +>E : Enum1 + + case Enum1.F: +>Enum1.F : Enum1 +>Enum1 : typeof Enum1 +>F : Enum1 + + case Enum1.G: +>Enum1.G : Enum1 +>Enum1 : typeof Enum1 +>G : Enum1 + + case Enum1.H: +>Enum1.H : Enum1 +>Enum1 : typeof Enum1 +>H : Enum1 + + case Enum1.I: +>Enum1.I : Enum1 +>Enum1 : typeof Enum1 +>I : Enum1 + + case Enum1.J: +>Enum1.J : Enum1 +>Enum1 : typeof Enum1 +>J : Enum1 + + case Enum1.K: +>Enum1.K : Enum1 +>Enum1 : typeof Enum1 +>K : Enum1 + + case Enum1.L: +>Enum1.L : Enum1 +>Enum1 : typeof Enum1 +>L : Enum1 + + case Enum1.M: +>Enum1.M : Enum1 +>Enum1 : typeof Enum1 +>M : Enum1 + + case Enum1.N: +>Enum1.N : Enum1 +>Enum1 : typeof Enum1 +>N : Enum1 + + case Enum1.O: +>Enum1.O : Enum1 +>Enum1 : typeof Enum1 +>O : Enum1 + + case Enum1.P: +>Enum1.P : Enum1 +>Enum1 : typeof Enum1 +>P : Enum1 + + case Enum1.Q: +>Enum1.Q : Enum1 +>Enum1 : typeof Enum1 +>Q : Enum1 + + case Enum1.R: +>Enum1.R : Enum1 +>Enum1 : typeof Enum1 +>R : Enum1 + + case Enum1.S: +>Enum1.S : Enum1 +>Enum1 : typeof Enum1 +>S : Enum1 + + case Enum1.T: +>Enum1.T : Enum1 +>Enum1 : typeof Enum1 +>T : Enum1 + + case Enum1.U: +>Enum1.U : Enum1 +>Enum1 : typeof Enum1 +>U : Enum1 + + case Enum1.V: +>Enum1.V : Enum1 +>Enum1 : typeof Enum1 +>V : Enum1 + + case Enum1.W: +>Enum1.W : Enum1 +>Enum1 : typeof Enum1 +>W : Enum1 + + case Enum1.W1: +>Enum1.W1 : Enum1 +>Enum1 : typeof Enum1 +>W1 : Enum1 + + case Enum1.W2: +>Enum1.W2 : Enum1 +>Enum1 : typeof Enum1 +>W2 : Enum1 + + case Enum1.W3: +>Enum1.W3 : Enum1 +>Enum1 : typeof Enum1 +>W3 : Enum1 + + case Enum1.W4: +>Enum1.W4 : Enum1 +>Enum1 : typeof Enum1 +>W4 : Enum1 + + break; + } +} + +function bar(e: A.B.C.E): number { +>bar : (e: I) => number +>e : I +>A : unknown +>B : unknown +>C : unknown +>E : I + + switch (e) { +>e : I + + case A.B.C.E.V1: return 1; +>A.B.C.E.V1 : I +>A.B.C.E : typeof I +>A.B.C : typeof A.B.C +>A.B : typeof A.B +>A : typeof A +>B : typeof A.B +>C : typeof A.B.C +>E : typeof I +>V1 : I + + case A.B.C.E.V2: return 1; +>A.B.C.E.V2 : I +>A.B.C.E : typeof I +>A.B.C : typeof A.B.C +>A.B : typeof A.B +>A : typeof A +>B : typeof A.B +>C : typeof A.B.C +>E : typeof I +>V2 : I + + case A.B.C.E.V3: return 1; +>A.B.C.E.V3 : I +>A.B.C.E : typeof I +>A.B.C : typeof A.B.C +>A.B : typeof A.B +>A : typeof A +>B : typeof A.B +>C : typeof A.B.C +>E : typeof I +>V3 : I + } +} diff --git a/tests/baselines/reference/constantsInEnumMembers.errors.txt b/tests/baselines/reference/constantsInEnumMembers.errors.txt deleted file mode 100644 index 323a352ebb275..0000000000000 --- a/tests/baselines/reference/constantsInEnumMembers.errors.txt +++ /dev/null @@ -1,122 +0,0 @@ -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; - } - } \ No newline at end of file diff --git a/tests/cases/compiler/constEnumErrors.ts b/tests/cases/compiler/constEnumErrors.ts new file mode 100644 index 0000000000000..b77a439928424 --- /dev/null +++ b/tests/cases/compiler/constEnumErrors.ts @@ -0,0 +1,32 @@ +const enum E { + A +} + +module E { + var x = 1; +} + +const enum E1 { + // illegal case + // forward reference to the element of the same enum + X = Y, + // forward reference to the element of the same enum + Y = E1.Z, + Y1 = E1["Z"] +} + +const enum E2 { + A +} + +var y0 = E2[1] +var name = "A"; +var y1 = E2[name]; + +var x = E2; +var y = [E2]; + +function foo(t: any): void { +} + +foo(E2); \ No newline at end of file diff --git a/tests/cases/compiler/constantsInEnumMembers.ts b/tests/cases/compiler/constEnums.ts similarity index 72% rename from tests/cases/compiler/constantsInEnumMembers.ts rename to tests/cases/compiler/constEnums.ts index dfaac3ffe0a66..3198b25d51fc5 100644 --- a/tests/cases/compiler/constantsInEnumMembers.ts +++ b/tests/cases/compiler/constEnums.ts @@ -7,14 +7,14 @@ const enum Enum1 { 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, + D = A | B, + E = A | 1, + F = 1 | A, + G = (1 & 1), + H = ~(A | B), + I = A >>> 1, + J = 1 & A, + K = ~(1 | 5), L = ~D, M = E << B, N = E << 1, @@ -33,13 +33,6 @@ const enum Enum1 { W2 = Enum1.A0, W3 = Enum1["A0"], W4 = Enum1["W"], - // 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, - Y1 = Enum1["Z"], - Z = 100, } @@ -48,7 +41,7 @@ module A { export module C { export const enum E { V1 = 1, - V2 = A.B.C.E.V1 + 100 + V2 = A.B.C.E.V1 | 100 } } } @@ -58,12 +51,21 @@ module A { export module B { export module C { export const enum E { - V3 = A.B.C.E["V2"] + 200, + V3 = A.B.C.E["V2"] & 200, } } } } +import I = A.B.C.E; + +function foo0(e: I): void { + if (e === I.V1) { + } + else if (e === I.V2) { + } +} + function foo(x: Enum1) { switch (x) { case Enum1.A: @@ -93,10 +95,6 @@ function foo(x: Enum1) { case Enum1.W2: case Enum1.W3: case Enum1.W4: - case Enum1.X: - case Enum1.Y: - case Enum1.Y1: - case Enum1.Z: break; } } From 4aa4ea75d1cecb96fca6e2d57b4b62832c2d913b Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 29 Oct 2014 23:21:30 -0700 Subject: [PATCH 09/16] allow arithmetic operations in constant expressions, handle infinity\NaN results --- src/compiler/checker.ts | 14 +++++++++++ .../diagnosticInformationMap.generated.ts | 2 ++ src/compiler/diagnosticMessages.json | 8 +++++++ .../reference/constEnumErrors.errors.txt | 24 +++++++++++++++++-- tests/baselines/reference/constEnumErrors.js | 13 +++++++++- tests/cases/compiler/constEnumErrors.ts | 13 +++++++++- 6 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 02757e458c811..4af17b4bcb0c5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7686,6 +7686,15 @@ module ts { checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*headMessage*/ undefined); } } + else if (enumIsConst) { + if (isNaN(autoValue)) { + error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_NaN); + } + else if (!isFinite(autoValue)) { + error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_number); + } + } + } else if (ambient && !enumIsConst) { autoValue = undefined; @@ -7735,6 +7744,11 @@ module ts { case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right; case SyntaxKind.LessThanLessThanToken: return left << right; case SyntaxKind.CaretToken: return left ^ right; + case SyntaxKind.AsteriskToken: return left * right; + case SyntaxKind.SlashToken: return left / right; + case SyntaxKind.PlusToken: return left + right; + case SyntaxKind.MinusToken: return left - right; + case SyntaxKind.PercentToken: return left % right; } return undefined; case SyntaxKind.NumericLiteral: diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index 8bf05e403c897..525932fb428c5 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -354,6 +354,8 @@ module ts { 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." }, 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." }, 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'." }, + const_enum_member_initializer_was_evaluated_to_a_non_finite_number: { code: 4086, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to a non-finite number." }, + const_enum_member_initializer_was_evaluated_to_NaN: { code: 4087, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to NaN." }, 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}" }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9b9094223ce08..3e79591e71967 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1417,6 +1417,14 @@ "category": "Error", "code": 4085 }, + "'const' enum member initializer was evaluated to a non-finite number.": { + "category": "Error", + "code": 4086 + }, + "'const' enum member initializer was evaluated to NaN.": { + "category": "Error", + "code": 4087 + }, "The current host does not support the '{0}' option.": { "category": "Error", "code": 5001 diff --git a/tests/baselines/reference/constEnumErrors.errors.txt b/tests/baselines/reference/constEnumErrors.errors.txt index 2464180ad3448..21cc5a239d52d 100644 --- a/tests/baselines/reference/constEnumErrors.errors.txt +++ b/tests/baselines/reference/constEnumErrors.errors.txt @@ -8,9 +8,12 @@ tests/cases/compiler/constEnumErrors.ts(24,13): error TS4085: Index expression a tests/cases/compiler/constEnumErrors.ts(26,9): error TS4084: 'const' enums can only be used in property access expressions. tests/cases/compiler/constEnumErrors.ts(27,10): error TS4084: 'const' enums can only be used in property access expressions. tests/cases/compiler/constEnumErrors.ts(32,5): error TS4084: 'const' enums can only be used in property access expressions. +tests/cases/compiler/constEnumErrors.ts(40,9): error TS4086: 'const' enum member initializer was evaluated to a non-finite number. +tests/cases/compiler/constEnumErrors.ts(41,9): error TS4086: 'const' enum member initializer was evaluated to a non-finite number. +tests/cases/compiler/constEnumErrors.ts(42,9): error TS4087: 'const' enum member initializer was evaluated to NaN. -==== tests/cases/compiler/constEnumErrors.ts (10 errors) ==== +==== tests/cases/compiler/constEnumErrors.ts (13 errors) ==== const enum E { ~ !!! error TS2300: Duplicate identifier 'E'. @@ -62,4 +65,21 @@ tests/cases/compiler/constEnumErrors.ts(32,5): error TS4084: 'const' enums can o foo(E2); ~~ -!!! error TS4084: 'const' enums can only be used in property access expressions. \ No newline at end of file +!!! error TS4084: 'const' enums can only be used in property access expressions. + + const enum NaNOrInfinity { + A = 9007199254740992, + B = A * A, + C = B * B, + D = C * C, + E = D * D, + F = E * E, // overflow + ~~~~~ +!!! error TS4086: 'const' enum member initializer was evaluated to a non-finite number. + G = 1 / 0, // overflow + ~~~~~ +!!! error TS4086: 'const' enum member initializer was evaluated to a non-finite number. + H = 0 / 0 // NaN + ~~~~~ +!!! error TS4087: 'const' enum member initializer was evaluated to NaN. + } \ No newline at end of file diff --git a/tests/baselines/reference/constEnumErrors.js b/tests/baselines/reference/constEnumErrors.js index 7a1260583df98..4afd98982f014 100644 --- a/tests/baselines/reference/constEnumErrors.js +++ b/tests/baselines/reference/constEnumErrors.js @@ -30,7 +30,18 @@ var y = [E2]; function foo(t: any): void { } -foo(E2); +foo(E2); + +const enum NaNOrInfinity { + A = 9007199254740992, + B = A * A, + C = B * B, + D = C * C, + E = D * D, + F = E * E, // overflow + G = 1 / 0, // overflow + H = 0 / 0 // NaN +} //// [constEnumErrors.js] var E; diff --git a/tests/cases/compiler/constEnumErrors.ts b/tests/cases/compiler/constEnumErrors.ts index b77a439928424..87e6b7ccb98fd 100644 --- a/tests/cases/compiler/constEnumErrors.ts +++ b/tests/cases/compiler/constEnumErrors.ts @@ -29,4 +29,15 @@ var y = [E2]; function foo(t: any): void { } -foo(E2); \ No newline at end of file +foo(E2); + +const enum NaNOrInfinity { + A = 9007199254740992, + B = A * A, + C = B * B, + D = C * C, + E = D * D, + F = E * E, // overflow + G = 1 / 0, // overflow + H = 0 / 0 // NaN +} \ No newline at end of file From 270d18711c891fb2857348e4648a2d0abac4b5f7 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Thu, 30 Oct 2014 00:24:08 -0700 Subject: [PATCH 10/16] addressed CR feedback --- src/compiler/binder.ts | 20 +++--- src/compiler/checker.ts | 33 ++++------ .../diagnosticInformationMap.generated.ts | 2 +- src/compiler/diagnosticMessages.json | 2 +- src/compiler/emitter.ts | 4 +- src/compiler/types.ts | 61 ++++++++++--------- src/services/breakpoints.ts | 6 +- src/services/services.ts | 4 +- .../reference/constEnumErrors.errors.txt | 12 ++-- .../reference/constEnumExternalModule.js | 19 ++++++ .../reference/constEnumExternalModule.types | 21 +++++++ .../cases/compiler/constEnumExternalModule.ts | 10 +++ 12 files changed, 119 insertions(+), 75 deletions(-) create mode 100644 tests/baselines/reference/constEnumExternalModule.js create mode 100644 tests/baselines/reference/constEnumExternalModule.types create mode 100644 tests/cases/compiler/constEnumExternalModule.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 81404c933efcc..bbb78bd2e583c 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -5,14 +5,14 @@ module ts { - export function isInstantiated(node: Node, checkConstEnums: boolean): boolean { + export function isInstantiated(node: Node, treatConstEnumsAsValues: boolean): boolean { // A module is uninstantiated if it contains only // 1. interface declarations if (node.kind === SyntaxKind.InterfaceDeclaration) { return false; } // 2. const enum declarations don't make module instantiated - else if (checkConstEnums && node.kind === SyntaxKind.EnumDeclaration && isConstEnumDeclaration(node)) { + else if (!treatConstEnumsAsValues && node.kind === SyntaxKind.EnumDeclaration && isConstEnumDeclaration(node)) { return false; } // 3. non - exported import declarations @@ -20,10 +20,10 @@ module ts { return false; } // 4. other uninstantiated module declarations. - else if (node.kind === SyntaxKind.ModuleBlock && !forEachChild(node, n => isInstantiated(n, checkConstEnums))) { + else if (node.kind === SyntaxKind.ModuleBlock && !forEachChild(node, n => isInstantiated(n, treatConstEnumsAsValues))) { return false; } - else if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated((node).body, checkConstEnums)) { + else if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated((node).body, treatConstEnumsAsValues)) { return false; } else { @@ -252,7 +252,7 @@ module ts { if (node.name.kind === SyntaxKind.StringLiteral) { bindDeclaration(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes, /*isBlockScopeContainer*/ true); } - else if (isInstantiated(node, /*checkConstEnums*/ false)) { + else if (isInstantiated(node, /*treatConstEnumsAsValues*/ true)) { bindDeclaration(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes, /*isBlockScopeContainer*/ true); } else { @@ -368,10 +368,12 @@ module ts { bindDeclaration(node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes, /*isBlockScopeContainer*/ false); break; case SyntaxKind.EnumDeclaration: - var enumIsConst = isConstEnumDeclaration(node); - var flags = enumIsConst ? SymbolFlags.Enum | SymbolFlags.ConstEnum : SymbolFlags.Enum; - var excludes = enumIsConst ? SymbolFlags.ConstEnumExcludes : SymbolFlags.EnumExcludes; - bindDeclaration(node, flags, excludes, /*isBlockScopeContainer*/ false); + if (isConstEnumDeclaration(node)) { + bindDeclaration(node, SymbolFlags.ConstEnum, SymbolFlags.ConstEnumExcludes, /*isBlockScopeContainer*/ false); + } + else { + bindDeclaration(node, SymbolFlags.RegularEnum, SymbolFlags.RegularEnumExcludes, /*isBlockScopeContainer*/ false); + } break; case SyntaxKind.ModuleDeclaration: bindModuleDeclaration(node); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4af17b4bcb0c5..efb99cac8a152 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -186,7 +186,8 @@ module ts { if (flags & SymbolFlags.Function) result |= SymbolFlags.FunctionExcludes; if (flags & SymbolFlags.Class) result |= SymbolFlags.ClassExcludes; if (flags & SymbolFlags.Interface) result |= SymbolFlags.InterfaceExcludes; - if (flags & SymbolFlags.Enum) result |= SymbolFlags.EnumExcludes; + if (flags & SymbolFlags.RegularEnum) result |= SymbolFlags.RegularEnumExcludes; + if (flags & SymbolFlags.ConstEnum) result |= SymbolFlags.ConstEnumExcludes; if (flags & SymbolFlags.ValueModule) result |= SymbolFlags.ValueModuleExcludes; if (flags & SymbolFlags.Method) result |= SymbolFlags.MethodExcludes; if (flags & SymbolFlags.GetAccessor) result |= SymbolFlags.GetAccessorExcludes; @@ -5086,7 +5087,7 @@ module ts { if (objectType === unknownType) return unknownType; - if (isConstEnumType(objectType) && node.index.kind !== SyntaxKind.StringLiteral) { + if (isConstEnumObjectType(objectType) && node.index.kind !== SyntaxKind.StringLiteral) { error(node.index, Diagnostics.Index_expression_arguments_in_const_enums_must_be_of_type_string); } @@ -5966,7 +5967,7 @@ module ts { return (type.flags & TypeFlags.Structured) !== 0; } - function isConstEnumType(type: Type) : boolean { + function isConstEnumObjectType(type: Type) : boolean { return type.flags & (TypeFlags.ObjectType | TypeFlags.Anonymous) && type.symbol && isConstEnumSymbol(type.symbol); } @@ -6199,7 +6200,7 @@ module ts { } } - if (isConstEnumType(type)) { + if (isConstEnumObjectType(type)) { // enum object type for const enums are only permitted in: // - 'left' in property access // - 'object' in indexed access @@ -6207,23 +6208,13 @@ module ts { var ok = (node.parent.kind === SyntaxKind.PropertyAccess && (node.parent).left === node) || (node.parent.kind === SyntaxKind.IndexedAccess && (node.parent).object === node) || - isRhsOfImportStatement(node); + ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(node)); if (!ok) { - error(node, Diagnostics.const_enums_can_only_be_used_in_property_access_expressions); + error(node, Diagnostics.const_enums_can_only_be_used_in_property_access_Slashindex_access_expressions_and_as_right_hand_side_in_import_declarations_Slashexport_assignments); } } return type; - - function isRhsOfImportStatement(n: Node): boolean { - while (n.parent) { - if (n.parent.kind === SyntaxKind.ImportDeclaration && (n.parent).entityName === n) { - return true; - } - n = n.parent; - } - return false; - } } function checkExpressionNode(node: Expression, contextualMapper: TypeMapper): Type { @@ -6892,7 +6883,7 @@ module ts { case SyntaxKind.InterfaceDeclaration: return SymbolFlags.ExportType; case SyntaxKind.ModuleDeclaration: - return (d).name.kind === SyntaxKind.StringLiteral || isInstantiated(d, /*checkConstEnums*/ false) + return (d).name.kind === SyntaxKind.StringLiteral || isInstantiated(d, /*treatConstEnumsAsValues*/ true) ? SymbolFlags.ExportNamespace | SymbolFlags.ExportValue : SymbolFlags.ExportNamespace; case SyntaxKind.ClassDeclaration: @@ -7129,7 +7120,7 @@ module ts { } // Uninstantiated modules shouldnt do this check - if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated(node, /*checkConstEnums*/ true)) { + if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated(node, /*treatConstEnumsAsValues*/ false)) { return; } @@ -8673,10 +8664,10 @@ module ts { function getExportAssignmentName(node: SourceFile): string { var symbol = getExportAssignmentSymbol(getSymbolOfNode(node)); - return symbol && symbolIsValue(symbol) ? symbolToString(symbol): undefined; + return symbol && symbolIsValue(symbol) && !isConstEnumSymbol(symbol) ? symbolToString(symbol): undefined; } - function isTopLevelValueImportedViaEntityName(node: ImportDeclaration): boolean { + function isTopLevelValueImportWithEntityName(node: ImportDeclaration): boolean { if (node.parent.kind !== SyntaxKind.SourceFile || !node.entityName) { // parent is not source file or it is not reference to internal module return false; @@ -8777,7 +8768,7 @@ module ts { isReferencedImportDeclaration: isReferencedImportDeclaration, getNodeCheckFlags: getNodeCheckFlags, getEnumMemberValue: getEnumMemberValue, - isTopLevelValueImportedViaEntityName: isTopLevelValueImportedViaEntityName, + isTopLevelValueImportWithEntityName: isTopLevelValueImportWithEntityName, hasSemanticErrors: hasSemanticErrors, hasEarlyErrors: hasEarlyErrors, isDeclarationVisible: isDeclarationVisible, diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index 525932fb428c5..c37cf3661eefd 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -352,7 +352,7 @@ module ts { Exported_type_alias_0_has_or_is_using_private_name_1: { code: 4081, category: DiagnosticCategory.Error, key: "Exported type alias '{0}' has or is using private name '{1}'." }, Enum_declarations_must_all_be_const_or_non_const: { code: 4082, category: DiagnosticCategory.Error, key: "Enum declarations must all be const or non-const." }, 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." }, - 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." }, + const_enums_can_only_be_used_in_property_access_Slashindex_access_expressions_and_as_right_hand_side_in_import_declarations_Slashexport_assignments: { code: 4084, category: DiagnosticCategory.Error, key: "'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments." }, 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'." }, const_enum_member_initializer_was_evaluated_to_a_non_finite_number: { code: 4086, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to a non-finite number." }, const_enum_member_initializer_was_evaluated_to_NaN: { code: 4087, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to NaN." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 3e79591e71967..b2a372f37949e 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1409,7 +1409,7 @@ "category": "Error", "code": 4083 }, - "'const' enums can only be used in property access expressions.": { + "'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments.": { "category": "Error", "code": 4084 }, diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 42992107eaac1..e466292897ab9 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1838,7 +1838,7 @@ module ts { } function emitModuleDeclaration(node: ModuleDeclaration) { - if (!isInstantiated(node, /*checkConstEnums*/ true)) { + if (!isInstantiated(node, /*treatConstEnumsAsValues*/ false)) { return emitPinnedOrTripleSlashComments(node); } emitLeadingComments(node); @@ -1890,7 +1890,7 @@ module ts { // preserve old compiler's behavior: emit 'var' for import declaration (even if we do not consider them referenced) when // - current file is not external module // - import declaration is top level and target is value imported by entity name - emitImportDeclaration = !isExternalModule(currentSourceFile) && resolver.isTopLevelValueImportedViaEntityName(node); + emitImportDeclaration = !isExternalModule(currentSourceFile) && resolver.isTopLevelValueImportWithEntityName(node); } if (emitImportDeclaration) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 055ae8b3e7738..5292a61ec27c0 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -734,7 +734,7 @@ module ts { getExpressionNamePrefix(node: Identifier): string; getExportAssignmentName(node: SourceFile): string; isReferencedImportDeclaration(node: ImportDeclaration): boolean; - isTopLevelValueImportedViaEntityName(node: ImportDeclaration): boolean; + isTopLevelValueImportWithEntityName(node: ImportDeclaration): boolean; getNodeCheckFlags(node: Node): NodeCheckFlags; getEnumMemberValue(node: EnumMember): number; hasSemanticErrors(): boolean; @@ -757,34 +757,35 @@ module ts { Function = 0x00000010, // Function Class = 0x00000020, // Class Interface = 0x00000040, // Interface - Enum = 0x00000080, // Enum - ValueModule = 0x00000100, // Instantiated module - NamespaceModule = 0x00000200, // Uninstantiated module - TypeLiteral = 0x00000400, // Type Literal - ObjectLiteral = 0x00000800, // Object Literal - Method = 0x00001000, // Method - Constructor = 0x00002000, // Constructor - GetAccessor = 0x00004000, // Get accessor - SetAccessor = 0x00008000, // Set accessor - CallSignature = 0x00010000, // Call signature - ConstructSignature = 0x00020000, // Construct signature - IndexSignature = 0x00040000, // Index signature - TypeParameter = 0x00080000, // Type parameter - TypeAlias = 0x00100000, // Type alias + ConstEnum = 0x00000080, // Const enum + RegularEnum = 0x00000100, // Enum + ValueModule = 0x00000200, // Instantiated module + NamespaceModule = 0x00000400, // Uninstantiated module + TypeLiteral = 0x00000800, // Type Literal + ObjectLiteral = 0x00001000, // Object Literal + Method = 0x00002000, // Method + Constructor = 0x00004000, // Constructor + GetAccessor = 0x00008000, // Get accessor + SetAccessor = 0x00010000, // Set accessor + CallSignature = 0x00020000, // Call signature + ConstructSignature = 0x00040000, // Construct signature + IndexSignature = 0x00080000, // Index signature + TypeParameter = 0x00100000, // Type parameter + TypeAlias = 0x00200000, // Type alias // Export markers (see comment in declareModuleMember in binder) - ExportValue = 0x00200000, // Exported value marker - ExportType = 0x00400000, // Exported type marker - ExportNamespace = 0x00800000, // Exported namespace marker - - Import = 0x01000000, // Import - Instantiated = 0x02000000, // Instantiated symbol - Merged = 0x04000000, // Merged symbol (created during program binding) - 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 - + ExportValue = 0x00400000, // Exported value marker + ExportType = 0x00800000, // Exported type marker + ExportNamespace = 0x01000000, // Exported namespace marker + + Import = 0x02000000, // Import + Instantiated = 0x04000000, // Instantiated symbol + Merged = 0x08000000, // Merged symbol (created during program binding) + Transient = 0x10000000, // Transient symbol (created during type check) + Prototype = 0x20000000, // Prototype property (no source representation) + UnionProperty = 0x40000000, // Property in union type + + Enum = RegularEnum | ConstEnum, Variable = FunctionScopedVariable | BlockScopedVariable, Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor, Type = Class | Interface | Enum | TypeLiteral | ObjectLiteral | TypeParameter | TypeAlias, @@ -807,9 +808,9 @@ module ts { FunctionExcludes = Value & ~(Function | ValueModule), ClassExcludes = (Value | Type) & ~ValueModule, InterfaceExcludes = Type & ~Interface, - EnumExcludes = (Value | Type) & ~(Enum | ValueModule), - ConstEnumExcludes = (Value | Type) & ~Enum, // const enums merge only with enums - ValueModuleExcludes = (Value | ConstEnum) & ~(Function | Class | Enum | ValueModule), + RegularEnumExcludes = (Value | Type) & ~(RegularEnum | ValueModule), // regular enums merge only with regular enums and modules + ConstEnumExcludes = (Value | Type) & ~ConstEnum, // const enums merge only with const enums + ValueModuleExcludes = Value & ~(Function | Class | RegularEnum | ValueModule), NamespaceModuleExcludes = 0, MethodExcludes = Value & ~Method, GetAccessorExcludes = Value & ~SetAccessor, diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index 319bb12d89418..b0457a82c71d6 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -178,7 +178,7 @@ module ts.BreakpointResolver { case SyntaxKind.ModuleDeclaration: // span on complete module if it is instantiated - if (!isInstantiated(node, /*checkConstEnums*/ true)) { + if (!isInstantiated(node, /*treatConstEnumsAsValues*/ false)) { return undefined; } @@ -350,7 +350,7 @@ module ts.BreakpointResolver { function spanInBlock(block: Block): TypeScript.TextSpan { switch (block.parent.kind) { case SyntaxKind.ModuleDeclaration: - if (!isInstantiated(block.parent, /*checkConstEnums*/ true)) { + if (!isInstantiated(block.parent, /*treatConstEnumsAsValues*/ false)) { return undefined; } @@ -407,7 +407,7 @@ module ts.BreakpointResolver { switch (node.parent.kind) { case SyntaxKind.ModuleBlock: // If this is not instantiated module block no bp span - if (!isInstantiated(node.parent.parent, /*checkConstEnums*/ true)) { + if (!isInstantiated(node.parent.parent, /*treatConstEnumsAsValues*/ false)) { return undefined; } diff --git a/src/services/services.ts b/src/services/services.ts index 0b14ad90c24c0..67a59778dfcac 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4549,7 +4549,7 @@ module ts { if ((node).name.kind === SyntaxKind.StringLiteral) { return SemanticMeaning.Namespace | SemanticMeaning.Value; } - else if (isInstantiated(node, /*checkConstEnums*/ true)) { + else if (isInstantiated(node, /*treatConstEnumsAsValues*/ false)) { return SemanticMeaning.Namespace | SemanticMeaning.Value; } else { @@ -4826,7 +4826,7 @@ module ts { */ function hasValueSideModule(symbol: Symbol): boolean { return forEach(symbol.declarations, declaration => { - return declaration.kind === SyntaxKind.ModuleDeclaration && isInstantiated(declaration, /*checkConstEnums*/ true); + return declaration.kind === SyntaxKind.ModuleDeclaration && isInstantiated(declaration, /*treatConstEnumsAsValues*/ false); }); } } diff --git a/tests/baselines/reference/constEnumErrors.errors.txt b/tests/baselines/reference/constEnumErrors.errors.txt index 21cc5a239d52d..78115a22dc44c 100644 --- a/tests/baselines/reference/constEnumErrors.errors.txt +++ b/tests/baselines/reference/constEnumErrors.errors.txt @@ -5,9 +5,9 @@ tests/cases/compiler/constEnumErrors.ts(14,9): error TS4083: In 'const' enum dec tests/cases/compiler/constEnumErrors.ts(15,10): error TS4083: In 'const' enum declarations member initializer must be constant expression. tests/cases/compiler/constEnumErrors.ts(22,13): error TS4085: Index expression arguments in 'const' enums must be of type 'string'. tests/cases/compiler/constEnumErrors.ts(24,13): error TS4085: Index expression arguments in 'const' enums must be of type 'string'. -tests/cases/compiler/constEnumErrors.ts(26,9): error TS4084: 'const' enums can only be used in property access expressions. -tests/cases/compiler/constEnumErrors.ts(27,10): error TS4084: 'const' enums can only be used in property access expressions. -tests/cases/compiler/constEnumErrors.ts(32,5): error TS4084: 'const' enums can only be used in property access expressions. +tests/cases/compiler/constEnumErrors.ts(26,9): error TS4084: 'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments. +tests/cases/compiler/constEnumErrors.ts(27,10): error TS4084: 'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments. +tests/cases/compiler/constEnumErrors.ts(32,5): error TS4084: 'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments. tests/cases/compiler/constEnumErrors.ts(40,9): error TS4086: 'const' enum member initializer was evaluated to a non-finite number. tests/cases/compiler/constEnumErrors.ts(41,9): error TS4086: 'const' enum member initializer was evaluated to a non-finite number. tests/cases/compiler/constEnumErrors.ts(42,9): error TS4087: 'const' enum member initializer was evaluated to NaN. @@ -55,17 +55,17 @@ tests/cases/compiler/constEnumErrors.ts(42,9): error TS4087: 'const' enum member var x = E2; ~~ -!!! error TS4084: 'const' enums can only be used in property access expressions. +!!! error TS4084: 'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments. var y = [E2]; ~~ -!!! error TS4084: 'const' enums can only be used in property access expressions. +!!! error TS4084: 'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments. function foo(t: any): void { } foo(E2); ~~ -!!! error TS4084: 'const' enums can only be used in property access expressions. +!!! error TS4084: 'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments. const enum NaNOrInfinity { A = 9007199254740992, diff --git a/tests/baselines/reference/constEnumExternalModule.js b/tests/baselines/reference/constEnumExternalModule.js new file mode 100644 index 0000000000000..4054ba788cb63 --- /dev/null +++ b/tests/baselines/reference/constEnumExternalModule.js @@ -0,0 +1,19 @@ +//// [tests/cases/compiler/constEnumExternalModule.ts] //// + +//// [m1.ts] +const enum E { + V = 100 +} + +export = E +//// [m2.ts] +import A = require('m1') +var v = A.V; + +//// [m1.js] +define(["require", "exports"], function (require, exports) { +}); +//// [m2.js] +define(["require", "exports"], function (require, exports) { + var v = 100 /* V */; +}); diff --git a/tests/baselines/reference/constEnumExternalModule.types b/tests/baselines/reference/constEnumExternalModule.types new file mode 100644 index 0000000000000..9c7b43a24d45a --- /dev/null +++ b/tests/baselines/reference/constEnumExternalModule.types @@ -0,0 +1,21 @@ +=== tests/cases/compiler/m2.ts === +import A = require('m1') +>A : typeof A + +var v = A.V; +>v : A +>A.V : A +>A : typeof A +>V : A + +=== tests/cases/compiler/m1.ts === +const enum E { +>E : E + + V = 100 +>V : E +} + +export = E +>E : E + diff --git a/tests/cases/compiler/constEnumExternalModule.ts b/tests/cases/compiler/constEnumExternalModule.ts new file mode 100644 index 0000000000000..d15240fe64d57 --- /dev/null +++ b/tests/cases/compiler/constEnumExternalModule.ts @@ -0,0 +1,10 @@ +//@module: amd +//@Filename: m1.ts +const enum E { + V = 100 +} + +export = E +//@Filename: m2.ts +import A = require('m1') +var v = A.V; \ No newline at end of file From dd57c6c5c0086ad0dc3877406606903b93e4e852 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Thu, 30 Oct 2014 22:48:32 -0700 Subject: [PATCH 11/16] added .d.ts generation tests --- .../reference/constEnumDeclarations.js | 28 +++++++++++++++++ .../reference/constEnumDeclarations.types | 30 +++++++++++++++++++ tests/cases/compiler/constEnumDeclarations.ts | 13 ++++++++ 3 files changed, 71 insertions(+) create mode 100644 tests/baselines/reference/constEnumDeclarations.js create mode 100644 tests/baselines/reference/constEnumDeclarations.types create mode 100644 tests/cases/compiler/constEnumDeclarations.ts diff --git a/tests/baselines/reference/constEnumDeclarations.js b/tests/baselines/reference/constEnumDeclarations.js new file mode 100644 index 0000000000000..1cbacb13bbd5f --- /dev/null +++ b/tests/baselines/reference/constEnumDeclarations.js @@ -0,0 +1,28 @@ +//// [constEnumDeclarations.ts] + +const enum E { + A = 1, + B = 2, + C = A | B +} + +const enum E2 { + A = 1, + B, + C +} + +//// [constEnumDeclarations.js] + + +//// [constEnumDeclarations.d.ts] +declare const enum E { + A = 1, + B = 2, + C = 3, +} +declare const enum E2 { + A = 1, + B = 2, + C = 3, +} diff --git a/tests/baselines/reference/constEnumDeclarations.types b/tests/baselines/reference/constEnumDeclarations.types new file mode 100644 index 0000000000000..9dc87eadef7c6 --- /dev/null +++ b/tests/baselines/reference/constEnumDeclarations.types @@ -0,0 +1,30 @@ +=== tests/cases/compiler/constEnumDeclarations.ts === + +const enum E { +>E : E + + A = 1, +>A : E + + B = 2, +>B : E + + C = A | B +>C : E +>A | B : number +>A : E +>B : E +} + +const enum E2 { +>E2 : E2 + + A = 1, +>A : E2 + + B, +>B : E2 + + C +>C : E2 +} diff --git a/tests/cases/compiler/constEnumDeclarations.ts b/tests/cases/compiler/constEnumDeclarations.ts new file mode 100644 index 0000000000000..c3c11f55aa38d --- /dev/null +++ b/tests/cases/compiler/constEnumDeclarations.ts @@ -0,0 +1,13 @@ +// @declaration: true + +const enum E { + A = 1, + B = 2, + C = A | B +} + +const enum E2 { + A = 1, + B, + C +} \ No newline at end of file From ac54fbfa462d930600da96933d6bb31df53d4a49 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Thu, 30 Oct 2014 23:05:18 -0700 Subject: [PATCH 12/16] set 'earlyError' bit to 'non-constant expression in constant enum initializer' error --- .../diagnosticInformationMap.generated.ts | 2 +- src/compiler/diagnosticMessages.json | 3 +- tests/baselines/reference/constEnumErrors.js | 58 ------------------- 3 files changed, 3 insertions(+), 60 deletions(-) delete mode 100644 tests/baselines/reference/constEnumErrors.js diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index c37cf3661eefd..ca724e5b6e62a 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -351,7 +351,7 @@ module ts { Exported_type_alias_0_has_or_is_using_name_1_from_private_module_2: { code: 4080, category: DiagnosticCategory.Error, key: "Exported type alias '{0}' has or is using name '{1}' from private module '{2}'." }, Exported_type_alias_0_has_or_is_using_private_name_1: { code: 4081, category: DiagnosticCategory.Error, key: "Exported type alias '{0}' has or is using private name '{1}'." }, Enum_declarations_must_all_be_const_or_non_const: { code: 4082, category: DiagnosticCategory.Error, key: "Enum declarations must all be const or non-const." }, - 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." }, + 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.", isEarly: true }, const_enums_can_only_be_used_in_property_access_Slashindex_access_expressions_and_as_right_hand_side_in_import_declarations_Slashexport_assignments: { code: 4084, category: DiagnosticCategory.Error, key: "'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments." }, 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'." }, const_enum_member_initializer_was_evaluated_to_a_non_finite_number: { code: 4086, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to a non-finite number." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b2a372f37949e..6649f470de302 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1407,7 +1407,8 @@ }, "In 'const' enum declarations member initializer must be constant expression.": { "category": "Error", - "code": 4083 + "code": 4083, + "isEarly": true }, "'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments.": { "category": "Error", diff --git a/tests/baselines/reference/constEnumErrors.js b/tests/baselines/reference/constEnumErrors.js deleted file mode 100644 index 4afd98982f014..0000000000000 --- a/tests/baselines/reference/constEnumErrors.js +++ /dev/null @@ -1,58 +0,0 @@ -//// [constEnumErrors.ts] -const enum E { - A -} - -module E { - var x = 1; -} - -const enum E1 { - // illegal case - // forward reference to the element of the same enum - X = Y, - // forward reference to the element of the same enum - Y = E1.Z, - Y1 = E1["Z"] -} - -const enum E2 { - A -} - -var y0 = E2[1] -var name = "A"; -var y1 = E2[name]; - -var x = E2; -var y = [E2]; - -function foo(t: any): void { -} - -foo(E2); - -const enum NaNOrInfinity { - A = 9007199254740992, - B = A * A, - C = B * B, - D = C * C, - E = D * D, - F = E * E, // overflow - G = 1 / 0, // overflow - H = 0 / 0 // NaN -} - -//// [constEnumErrors.js] -var E; -(function (E) { - var x = 1; -})(E || (E = {})); -var y0 = E2[1]; -var name = "A"; -var y1 = E2[name]; -var x = E2; -var y = [E2]; -function foo(t) { -} -foo(E2); From 7d80b7186dcb7e0af889832686d9eac45d974735 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Sat, 1 Nov 2014 00:56:00 -0700 Subject: [PATCH 13/16] do not treat module that contains only const enums as instantiated --- src/compiler/binder.ts | 61 +++++++--- src/compiler/checker.ts | 32 ++++-- src/compiler/emitter.ts | 2 +- src/compiler/types.ts | 3 +- src/services/breakpoints.ts | 6 +- src/services/services.ts | 4 +- tests/baselines/reference/constEnums.js | 67 +++++++++++ tests/baselines/reference/constEnums.types | 124 +++++++++++++++++++++ tests/cases/compiler/constEnums.ts | 43 +++++++ 9 files changed, 310 insertions(+), 32 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index bbb78bd2e583c..82fdaf5cdcd5d 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -5,29 +5,51 @@ module ts { - export function isInstantiated(node: Node, treatConstEnumsAsValues: boolean): boolean { + export enum ModuleInstanceState { + NonInstantiated = 0, + Instantiated = 1, + ConstEnumOnly = 2 + } + + export function getModuleInstanceState(node: Node): ModuleInstanceState { // A module is uninstantiated if it contains only // 1. interface declarations if (node.kind === SyntaxKind.InterfaceDeclaration) { - return false; + return ModuleInstanceState.NonInstantiated; } // 2. const enum declarations don't make module instantiated - else if (!treatConstEnumsAsValues && node.kind === SyntaxKind.EnumDeclaration && isConstEnumDeclaration(node)) { - return false; + else if (node.kind === SyntaxKind.EnumDeclaration && isConstEnumDeclaration(node)) { + return ModuleInstanceState.ConstEnumOnly; } // 3. non - exported import declarations else if (node.kind === SyntaxKind.ImportDeclaration && !(node.flags & NodeFlags.Export)) { - return false; + return ModuleInstanceState.NonInstantiated; } // 4. other uninstantiated module declarations. - else if (node.kind === SyntaxKind.ModuleBlock && !forEachChild(node, n => isInstantiated(n, treatConstEnumsAsValues))) { - return false; + else if (node.kind === SyntaxKind.ModuleBlock) { + var state = ModuleInstanceState.NonInstantiated; + forEachChild(node, n => { + switch (getModuleInstanceState(n)) { + case ModuleInstanceState.NonInstantiated: + // child is non-instantiated - continue searching + return false; + case ModuleInstanceState.ConstEnumOnly: + // child is const enum only - record state and continue searching + state = ModuleInstanceState.ConstEnumOnly; + return false; + case ModuleInstanceState.Instantiated: + // child is instantiated - record state and stop + state = ModuleInstanceState.Instantiated; + return true; + } + }); + return state; } - else if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated((node).body, treatConstEnumsAsValues)) { - return false; + else if (node.kind === SyntaxKind.ModuleDeclaration) { + return getModuleInstanceState((node).body); } else { - return true; + return ModuleInstanceState.Instantiated; } } @@ -252,11 +274,22 @@ module ts { if (node.name.kind === SyntaxKind.StringLiteral) { bindDeclaration(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes, /*isBlockScopeContainer*/ true); } - else if (isInstantiated(node, /*treatConstEnumsAsValues*/ true)) { - bindDeclaration(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes, /*isBlockScopeContainer*/ true); - } else { - bindDeclaration(node, SymbolFlags.NamespaceModule, SymbolFlags.NamespaceModuleExcludes, /*isBlockScopeContainer*/ true); + var state = getModuleInstanceState(node); + if (state === ModuleInstanceState.NonInstantiated) { + bindDeclaration(node, SymbolFlags.NamespaceModule, SymbolFlags.NamespaceModuleExcludes, /*isBlockScopeContainer*/ true); + } + else { + bindDeclaration(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes, /*isBlockScopeContainer*/ true); + if (state === ModuleInstanceState.ConstEnumOnly) { + // mark value module as module that contains only enums + node.symbol.constEnumOnlyModule = true; + } + else if (node.symbol.constEnumOnlyModule) { + // const only value module was merged with instantiated module - reset flag + node.symbol.constEnumOnlyModule = false; + } + } } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index efb99cac8a152..c64da385e9c0f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -208,6 +208,7 @@ module ts { result.declarations = symbol.declarations.slice(0); result.parent = symbol.parent; if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; + if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; if (symbol.members) result.members = cloneSymbolTable(symbol.members); if (symbol.exports) result.exports = cloneSymbolTable(symbol.exports); recordMergedSymbol(result, symbol); @@ -216,6 +217,10 @@ module ts { function extendSymbol(target: Symbol, source: Symbol) { if (!(target.flags & getExcludedSymbolFlags(source.flags))) { + if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) { + // reset flag when merging instantiated module into value module that has only const enums + target.constEnumOnlyModule = false; + } target.flags |= source.flags; if (!target.valueDeclaration && source.valueDeclaration) target.valueDeclaration = source.valueDeclaration; forEach(source.declarations, node => { @@ -4432,8 +4437,8 @@ module ts { if (symbol.flags & SymbolFlags.Import) { // Mark the import as referenced so that we emit it in the final .js file. - // exception: identifiers that appear in type queries, const enums - getSymbolLinks(symbol).referenced = !isInTypeQuery(node) && !isConstEnumSymbol(resolveImport(symbol)); + // exception: identifiers that appear in type queries, const enums, modules that contain only const enums + getSymbolLinks(symbol).referenced = !isInTypeQuery(node) && !isConstEnumOrConstEnumOnlyModule(resolveImport(symbol)); } checkCollisionWithCapturedSuperVariable(node, node); @@ -6883,7 +6888,7 @@ module ts { case SyntaxKind.InterfaceDeclaration: return SymbolFlags.ExportType; case SyntaxKind.ModuleDeclaration: - return (d).name.kind === SyntaxKind.StringLiteral || isInstantiated(d, /*treatConstEnumsAsValues*/ true) + return (d).name.kind === SyntaxKind.StringLiteral || getModuleInstanceState(d) !== ModuleInstanceState.NonInstantiated ? SymbolFlags.ExportNamespace | SymbolFlags.ExportValue : SymbolFlags.ExportNamespace; case SyntaxKind.ClassDeclaration: @@ -7120,7 +7125,7 @@ module ts { } // Uninstantiated modules shouldnt do this check - if (node.kind === SyntaxKind.ModuleDeclaration && !isInstantiated(node, /*treatConstEnumsAsValues*/ false)) { + if (node.kind === SyntaxKind.ModuleDeclaration && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { return; } @@ -8673,8 +8678,7 @@ module ts { return false; } var symbol = getSymbolOfNode(node); - var target = resolveImport(symbol); - return target !== unknownSymbol && ((target.flags & SymbolFlags.Value) !== 0) && !isConstEnumSymbol(target); + return isImportResolvedToValue(getSymbolOfNode(node)); } function hasSemanticErrors() { @@ -8686,6 +8690,16 @@ module ts { return forEach(getDiagnostics(sourceFile), d => d.isEarly); } + function isImportResolvedToValue(symbol: Symbol): boolean { + var target = resolveImport(symbol); + // const enums and modules that contain only const enums are not considered values from the emit perespective + return target !== unknownSymbol && target.flags & SymbolFlags.Value && !isConstEnumOrConstEnumOnlyModule(target); + } + + function isConstEnumOrConstEnumOnlyModule(s: Symbol): boolean { + return isConstEnumSymbol(s) || s.constEnumOnlyModule; + } + function isReferencedImportDeclaration(node: ImportDeclaration): boolean { var symbol = getSymbolOfNode(node); if (getSymbolLinks(symbol).referenced) { @@ -8694,11 +8708,7 @@ module ts { // logic below will answer 'true' for exported import declaration in a nested module that itself is not exported. // As a consequence this might cause emitting extra. if (node.flags & NodeFlags.Export) { - var target = resolveImport(symbol); - // importing const enum does not cause import to be referenced - if (target !== unknownSymbol && target.flags & SymbolFlags.Value && !isConstEnumSymbol(target)) { - return true; - } + return isImportResolvedToValue(symbol); } return false; } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index e466292897ab9..5c05e7b30f9ce 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1838,7 +1838,7 @@ module ts { } function emitModuleDeclaration(node: ModuleDeclaration) { - if (!isInstantiated(node, /*treatConstEnumsAsValues*/ false)) { + if (getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { return emitPinnedOrTripleSlashComments(node); } emitLeadingComments(node); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5292a61ec27c0..ae7314d625fb9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -842,7 +842,8 @@ module ts { members?: SymbolTable; // Class, interface or literal instance members exports?: SymbolTable; // Module exports exportSymbol?: Symbol; // Exported symbol associated with this symbol - valueDeclaration?: Declaration // First value declaration of the symbol + valueDeclaration?: Declaration // First value declaration of the symbol, + constEnumOnlyModule?: boolean // For modules - if true - module contains only const enums or other modules with only const enums. } export interface SymbolLinks { diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index b0457a82c71d6..3a82e71c677a3 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -178,7 +178,7 @@ module ts.BreakpointResolver { case SyntaxKind.ModuleDeclaration: // span on complete module if it is instantiated - if (!isInstantiated(node, /*treatConstEnumsAsValues*/ false)) { + if (getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { return undefined; } @@ -350,7 +350,7 @@ module ts.BreakpointResolver { function spanInBlock(block: Block): TypeScript.TextSpan { switch (block.parent.kind) { case SyntaxKind.ModuleDeclaration: - if (!isInstantiated(block.parent, /*treatConstEnumsAsValues*/ false)) { + if (getModuleInstanceState(block.parent) !== ModuleInstanceState.Instantiated) { return undefined; } @@ -407,7 +407,7 @@ module ts.BreakpointResolver { switch (node.parent.kind) { case SyntaxKind.ModuleBlock: // If this is not instantiated module block no bp span - if (!isInstantiated(node.parent.parent, /*treatConstEnumsAsValues*/ false)) { + if (getModuleInstanceState(node.parent.parent) !== ModuleInstanceState.Instantiated) { return undefined; } diff --git a/src/services/services.ts b/src/services/services.ts index 67a59778dfcac..d74ba6375b6d3 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4549,7 +4549,7 @@ module ts { if ((node).name.kind === SyntaxKind.StringLiteral) { return SemanticMeaning.Namespace | SemanticMeaning.Value; } - else if (isInstantiated(node, /*treatConstEnumsAsValues*/ false)) { + else if (getModuleInstanceState(node) === ModuleInstanceState.Instantiated) { return SemanticMeaning.Namespace | SemanticMeaning.Value; } else { @@ -4826,7 +4826,7 @@ module ts { */ function hasValueSideModule(symbol: Symbol): boolean { return forEach(symbol.declarations, declaration => { - return declaration.kind === SyntaxKind.ModuleDeclaration && isInstantiated(declaration, /*treatConstEnumsAsValues*/ false); + return declaration.kind === SyntaxKind.ModuleDeclaration && getModuleInstanceState(declaration) == ModuleInstanceState.Instantiated; }); } } diff --git a/tests/baselines/reference/constEnums.js b/tests/baselines/reference/constEnums.js index aa19b8e9230b3..6792c4f8beaa5 100644 --- a/tests/baselines/reference/constEnums.js +++ b/tests/baselines/reference/constEnums.js @@ -58,7 +58,35 @@ module A { } } +module A1 { + export module B { + export module C { + export const enum E { + V1 = 10, + V2 = 110, + } + } + } +} + +module A2 { + export module B { + export module C { + export const enum E { + V1 = 10, + V2 = 110, + } + } + // module C will be classified as value + export module C { + var x = 1 + } + } +} + import I = A.B.C.E; +import I1 = A1.B; +import I2 = A2.B; function foo0(e: I): void { if (e === I.V1) { @@ -67,6 +95,21 @@ function foo0(e: I): void { } } +function foo1(e: I1.C.E): void { + if (e === I1.C.E.V1) { + } + else if (e === I1.C.E.V2) { + } +} + +function foo2(e: I2.C.E): void { + if (e === I2.C.E.V1) { + } + else if (e === I2.C.E.V2) { + } +} + + function foo(x: Enum1) { switch (x) { case Enum1.A: @@ -109,12 +152,36 @@ function bar(e: A.B.C.E): number { } //// [constEnums.js] +var A2; +(function (A2) { + var B; + (function (B) { + // module C will be classified as value + var C; + (function (C) { + var x = 1; + })(C = B.C || (B.C = {})); + })(B = A2.B || (A2.B = {})); +})(A2 || (A2 = {})); +var I2 = A2.B; function foo0(e) { if (e === 1 /* V1 */) { } else if (e === 101 /* V2 */) { } } +function foo1(e) { + if (e === 10 /* V1 */) { + } + else if (e === 110 /* V2 */) { + } +} +function foo2(e) { + if (e === 10 /* V1 */) { + } + else if (e === 110 /* V2 */) { + } +} function foo(x) { switch (x) { case 0 /* A */: diff --git a/tests/baselines/reference/constEnums.types b/tests/baselines/reference/constEnums.types index 3ce0eeac9e306..6197d61dc795d 100644 --- a/tests/baselines/reference/constEnums.types +++ b/tests/baselines/reference/constEnums.types @@ -211,6 +211,57 @@ module A { } } +module A1 { +>A1 : typeof A1 + + export module B { +>B : typeof B + + export module C { +>C : typeof C + + export const enum E { +>E : E + + V1 = 10, +>V1 : E + + V2 = 110, +>V2 : E + } + } + } +} + +module A2 { +>A2 : typeof A2 + + export module B { +>B : typeof B + + export module C { +>C : typeof C + + export const enum E { +>E : E + + V1 = 10, +>V1 : E + + V2 = 110, +>V2 : E + } + } + // module C will be classified as value + export module C { +>C : typeof C + + var x = 1 +>x : number + } + } +} + import I = A.B.C.E; >I : typeof I >A : typeof A @@ -218,6 +269,16 @@ import I = A.B.C.E; >C : typeof A.B.C >E : I +import I1 = A1.B; +>I1 : typeof I1 +>A1 : typeof A1 +>B : typeof I1 + +import I2 = A2.B; +>I2 : typeof I2 +>A2 : typeof A2 +>B : typeof I2 + function foo0(e: I): void { >foo0 : (e: I) => void >e : I @@ -239,6 +300,69 @@ function foo0(e: I): void { } } +function foo1(e: I1.C.E): void { +>foo1 : (e: I1.C.E) => void +>e : I1.C.E +>I1 : unknown +>C : unknown +>E : I1.C.E + + if (e === I1.C.E.V1) { +>e === I1.C.E.V1 : boolean +>e : I1.C.E +>I1.C.E.V1 : I1.C.E +>I1.C.E : typeof I1.C.E +>I1.C : typeof I1.C +>I1 : typeof I1 +>C : typeof I1.C +>E : typeof I1.C.E +>V1 : I1.C.E + } + else if (e === I1.C.E.V2) { +>e === I1.C.E.V2 : boolean +>e : I1.C.E +>I1.C.E.V2 : I1.C.E +>I1.C.E : typeof I1.C.E +>I1.C : typeof I1.C +>I1 : typeof I1 +>C : typeof I1.C +>E : typeof I1.C.E +>V2 : I1.C.E + } +} + +function foo2(e: I2.C.E): void { +>foo2 : (e: I2.C.E) => void +>e : I2.C.E +>I2 : unknown +>C : unknown +>E : I2.C.E + + if (e === I2.C.E.V1) { +>e === I2.C.E.V1 : boolean +>e : I2.C.E +>I2.C.E.V1 : I2.C.E +>I2.C.E : typeof I2.C.E +>I2.C : typeof I2.C +>I2 : typeof I2 +>C : typeof I2.C +>E : typeof I2.C.E +>V1 : I2.C.E + } + else if (e === I2.C.E.V2) { +>e === I2.C.E.V2 : boolean +>e : I2.C.E +>I2.C.E.V2 : I2.C.E +>I2.C.E : typeof I2.C.E +>I2.C : typeof I2.C +>I2 : typeof I2 +>C : typeof I2.C +>E : typeof I2.C.E +>V2 : I2.C.E + } +} + + function foo(x: Enum1) { >foo : (x: Enum1) => void >x : Enum1 diff --git a/tests/cases/compiler/constEnums.ts b/tests/cases/compiler/constEnums.ts index 3198b25d51fc5..504ad3e5a7579 100644 --- a/tests/cases/compiler/constEnums.ts +++ b/tests/cases/compiler/constEnums.ts @@ -57,7 +57,35 @@ module A { } } +module A1 { + export module B { + export module C { + export const enum E { + V1 = 10, + V2 = 110, + } + } + } +} + +module A2 { + export module B { + export module C { + export const enum E { + V1 = 10, + V2 = 110, + } + } + // module C will be classified as value + export module C { + var x = 1 + } + } +} + import I = A.B.C.E; +import I1 = A1.B; +import I2 = A2.B; function foo0(e: I): void { if (e === I.V1) { @@ -66,6 +94,21 @@ function foo0(e: I): void { } } +function foo1(e: I1.C.E): void { + if (e === I1.C.E.V1) { + } + else if (e === I1.C.E.V2) { + } +} + +function foo2(e: I2.C.E): void { + if (e === I2.C.E.V1) { + } + else if (e === I2.C.E.V2) { + } +} + + function foo(x: Enum1) { switch (x) { case Enum1.A: From 8662c689b31fc56e393a4fff72175e5bdd438e74 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Sat, 1 Nov 2014 01:06:06 -0700 Subject: [PATCH 14/16] add test for 'preserveConstEnums' command line argument --- src/harness/harness.ts | 5 ++++- tests/baselines/reference/preserveConstEnums.js | 10 ++++++++++ tests/baselines/reference/preserveConstEnums.types | 7 +++++++ tests/cases/compiler/preserveConstEnums.ts | 4 ++++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/preserveConstEnums.js create mode 100644 tests/baselines/reference/preserveConstEnums.types create mode 100644 tests/cases/compiler/preserveConstEnums.ts diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 2e837c7887d06..3b64868504166 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -771,6 +771,9 @@ module Harness { case 'errortruncation': options.noErrorTruncation = setting.value === 'false'; break; + case 'preserveconstenums': + options.preserveConstEnums = setting.value === 'true'; + break; default: throw new Error('Unsupported compiler setting ' + setting.flag); } @@ -1145,7 +1148,7 @@ module Harness { var optionRegex = /^[\/]{2}\s*@(\w+)\s*:\s*(\S*)/gm; // multiple matches on multiple lines // List of allowed metadata names - var fileMetadataNames = ["filename", "comments", "declaration", "module", "nolib", "sourcemap", "target", "out", "outdir", "noimplicitany", "noresolve", "newline", "newlines", "emitbom", "errortruncation", "usecasesensitivefilenames"]; + var fileMetadataNames = ["filename", "comments", "declaration", "module", "nolib", "sourcemap", "target", "out", "outdir", "noimplicitany", "noresolve", "newline", "newlines", "emitbom", "errortruncation", "usecasesensitivefilenames", "preserveconstenums"]; function extractCompilerSettings(content: string): CompilerSetting[] { diff --git a/tests/baselines/reference/preserveConstEnums.js b/tests/baselines/reference/preserveConstEnums.js new file mode 100644 index 0000000000000..541c4488ec842 --- /dev/null +++ b/tests/baselines/reference/preserveConstEnums.js @@ -0,0 +1,10 @@ +//// [preserveConstEnums.ts] +const enum E { + Value = 1 +} + +//// [preserveConstEnums.js] +var E; +(function (E) { + E[E["Value"] = 1] = "Value"; +})(E || (E = {})); diff --git a/tests/baselines/reference/preserveConstEnums.types b/tests/baselines/reference/preserveConstEnums.types new file mode 100644 index 0000000000000..7e886caefe6ed --- /dev/null +++ b/tests/baselines/reference/preserveConstEnums.types @@ -0,0 +1,7 @@ +=== tests/cases/compiler/preserveConstEnums.ts === +const enum E { +>E : E + + Value = 1 +>Value : E +} diff --git a/tests/cases/compiler/preserveConstEnums.ts b/tests/cases/compiler/preserveConstEnums.ts new file mode 100644 index 0000000000000..58e07930cc129 --- /dev/null +++ b/tests/cases/compiler/preserveConstEnums.ts @@ -0,0 +1,4 @@ +// @preserveConstEnums: true +const enum E { + Value = 1 +} \ No newline at end of file From 2d94030f58a12d308e1b996cb859a4cc69082c9e Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Sat, 1 Nov 2014 18:16:48 -0700 Subject: [PATCH 15/16] inline enum constant values for indexed access when index is string literal --- src/compiler/checker.ts | 3 ++- src/compiler/emitter.ts | 22 ++++++++++++++----- src/compiler/types.ts | 2 +- ...onOperatorWithNullValueAndValidOperator.js | 4 ++-- .../additionOperatorWithNumberAndEnum.js | 2 +- ...ratorWithUndefinedValueAndValidOperator.js | 4 ++-- .../bitwiseNotOperatorWithEnumType.js | 10 ++++----- tests/baselines/reference/constEnums.js | 4 ++-- tests/baselines/reference/constEnums.types | 5 ++--- .../decrementOperatorWithEnumType.js | 4 ++-- .../reference/deleteOperatorWithEnumType.js | 6 ++--- .../incrementOperatorWithEnumType.js | 4 ++-- .../logicalNotOperatorWithEnumType.js | 6 ++--- .../reference/negateOperatorWithEnumType.js | 6 ++--- .../reference/noImplicitAnyIndexing.js | 2 +- .../reference/plusOperatorWithEnumType.js | 4 ++-- .../reference/typeofOperatorWithEnumType.js | 6 ++--- .../reference/voidOperatorWithEnumType.js | 6 ++--- tests/cases/compiler/constEnums.ts | 2 +- 19 files changed, 56 insertions(+), 46 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 417c949e1a787..64746d75417fe 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5136,6 +5136,7 @@ module ts { var name = (node.index).text; var prop = getPropertyOfType(objectType, name); if (prop) { + getNodeLinks(node).resolvedSymbol = prop; return getTypeOfSymbol(prop); } } @@ -8833,7 +8834,7 @@ module ts { return getNodeLinks(node).enumMemberValue; } - function getConstantValue(node: PropertyAccess): number { + function getConstantValue(node: PropertyAccess | IndexedAccess): number { var symbol = getNodeLinks(node).resolvedSymbol; if (symbol && (symbol.flags & SymbolFlags.EnumMember)) { var declaration = symbol.valueDeclaration; diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 70883bc84617f..e39f35011fa8f 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1031,19 +1031,29 @@ module ts { emitTrailingComments(node); } - function emitPropertyAccess(node: PropertyAccess) { + function tryEmitConstantValue(node: PropertyAccess | IndexedAccess): boolean { var constantValue = resolver.getConstantValue(node); if (constantValue !== undefined) { - write(constantValue.toString() + " /* " + identifierToString(node.right) + " */"); + var propertyName = node.kind === SyntaxKind.PropertyAccess ? identifierToString((node).right) : getTextOfNode((node).index); + write(constantValue.toString() + " /* " + propertyName + " */"); + return true; } - else { - emit(node.left); - write("."); - emit(node.right); + return false; + } + + function emitPropertyAccess(node: PropertyAccess) { + if (tryEmitConstantValue(node)) { + return; } + emit(node.left); + write("."); + emit(node.right); } function emitIndexedAccess(node: IndexedAccess) { + if (tryEmitConstantValue(node)) { + return; + } emit(node.object); write("["); emit(node.index); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 83f0b64788493..bd43fe94ec850 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -774,7 +774,7 @@ module ts { isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags): SymbolAccessiblityResult; isImportDeclarationEntityNameReferenceDeclarationVisibile(entityName: EntityName): SymbolAccessiblityResult; // Returns the constant value this property access resolves to, or 'undefined' for a non-constant - getConstantValue(node: PropertyAccess): number; + getConstantValue(node: PropertyAccess | IndexedAccess): number; hasEarlyErrors(sourceFile?: SourceFile): boolean; } diff --git a/tests/baselines/reference/additionOperatorWithNullValueAndValidOperator.js b/tests/baselines/reference/additionOperatorWithNullValueAndValidOperator.js index f34ac61d86d55..1ba54d47f1cb3 100644 --- a/tests/baselines/reference/additionOperatorWithNullValueAndValidOperator.js +++ b/tests/baselines/reference/additionOperatorWithNullValueAndValidOperator.js @@ -50,12 +50,12 @@ var r3 = null + b; var r4 = null + 1; var r5 = null + c; var r6 = null + 0 /* a */; -var r7 = null + E['a']; +var r7 = null + 0 /* 'a' */; var r8 = b + null; var r9 = 1 + null; var r10 = c + null; var r11 = 0 /* a */ + null; -var r12 = E['a'] + null; +var r12 = 0 /* 'a' */ + null; // null + string var r13 = null + d; var r14 = null + ''; diff --git a/tests/baselines/reference/additionOperatorWithNumberAndEnum.js b/tests/baselines/reference/additionOperatorWithNumberAndEnum.js index 63e038265d64c..05bca7a4c0cd5 100644 --- a/tests/baselines/reference/additionOperatorWithNumberAndEnum.js +++ b/tests/baselines/reference/additionOperatorWithNumberAndEnum.js @@ -29,4 +29,4 @@ var r4 = b + b; var r5 = 0 + a; var r6 = 0 /* a */ + 0; var r7 = 0 /* a */ + 1 /* b */; -var r8 = E['a'] + E['b']; +var r8 = 0 /* 'a' */ + 1 /* 'b' */; diff --git a/tests/baselines/reference/additionOperatorWithUndefinedValueAndValidOperator.js b/tests/baselines/reference/additionOperatorWithUndefinedValueAndValidOperator.js index a6a1b2ef555c3..cb89d242a18a6 100644 --- a/tests/baselines/reference/additionOperatorWithUndefinedValueAndValidOperator.js +++ b/tests/baselines/reference/additionOperatorWithUndefinedValueAndValidOperator.js @@ -50,12 +50,12 @@ var r3 = undefined + b; var r4 = undefined + 1; var r5 = undefined + c; var r6 = undefined + 0 /* a */; -var r7 = undefined + E['a']; +var r7 = undefined + 0 /* 'a' */; var r8 = b + undefined; var r9 = 1 + undefined; var r10 = c + undefined; var r11 = 0 /* a */ + undefined; -var r12 = E['a'] + undefined; +var r12 = 0 /* 'a' */ + undefined; // undefined + string var r13 = undefined + d; var r14 = undefined + ''; diff --git a/tests/baselines/reference/bitwiseNotOperatorWithEnumType.js b/tests/baselines/reference/bitwiseNotOperatorWithEnumType.js index 9cd5a63912e09..710af4edfa779 100644 --- a/tests/baselines/reference/bitwiseNotOperatorWithEnumType.js +++ b/tests/baselines/reference/bitwiseNotOperatorWithEnumType.js @@ -30,11 +30,11 @@ var ENUM1; // enum type var var ResultIsNumber1 = ~ENUM1; // enum type expressions -var ResultIsNumber2 = ~ENUM1["A"]; -var ResultIsNumber3 = ~(0 /* A */ + ENUM1["B"]); +var ResultIsNumber2 = ~0 /* "A" */; +var ResultIsNumber3 = ~(0 /* A */ + 1 /* "B" */); // multiple ~ operators -var ResultIsNumber4 = ~~~(ENUM1["A"] + 1 /* B */); +var ResultIsNumber4 = ~~~(0 /* "A" */ + 1 /* B */); // miss assignment operators ~ENUM1; -~ENUM1["A"]; -~0 /* A */, ~ENUM1["B"]; +~0 /* "A" */; +~0 /* A */, ~1 /* "B" */; diff --git a/tests/baselines/reference/constEnums.js b/tests/baselines/reference/constEnums.js index 6792c4f8beaa5..b7077c74df1da 100644 --- a/tests/baselines/reference/constEnums.js +++ b/tests/baselines/reference/constEnums.js @@ -131,7 +131,7 @@ function foo(x: Enum1) { case Enum1.Q: case Enum1.R: case Enum1.S: - case Enum1.T: + case Enum1["T"]: case Enum1.U: case Enum1.V: case Enum1.W: @@ -203,7 +203,7 @@ function foo(x) { case -1 /* Q */: case 0 /* R */: case 0 /* S */: - case 11 /* T */: + case 11 /* "T" */: case 11 /* U */: case 11 /* V */: case 11 /* W */: diff --git a/tests/baselines/reference/constEnums.types b/tests/baselines/reference/constEnums.types index 6197d61dc795d..396b2e1c32b52 100644 --- a/tests/baselines/reference/constEnums.types +++ b/tests/baselines/reference/constEnums.types @@ -466,10 +466,9 @@ function foo(x: Enum1) { >Enum1 : typeof Enum1 >S : Enum1 - case Enum1.T: ->Enum1.T : Enum1 + case Enum1["T"]: +>Enum1["T"] : Enum1 >Enum1 : typeof Enum1 ->T : Enum1 case Enum1.U: >Enum1.U : Enum1 diff --git a/tests/baselines/reference/decrementOperatorWithEnumType.js b/tests/baselines/reference/decrementOperatorWithEnumType.js index 13947a6a9488c..15247ea4225b6 100644 --- a/tests/baselines/reference/decrementOperatorWithEnumType.js +++ b/tests/baselines/reference/decrementOperatorWithEnumType.js @@ -22,8 +22,8 @@ var ENUM1; })(ENUM1 || (ENUM1 = {})); ; // expression -var ResultIsNumber1 = --ENUM1["A"]; +var ResultIsNumber1 = --0 /* "A" */; var ResultIsNumber2 = 0 /* A */--; // miss assignment operator ---ENUM1["A"]; +--0 /* "A" */; ENUM1[A]--; diff --git a/tests/baselines/reference/deleteOperatorWithEnumType.js b/tests/baselines/reference/deleteOperatorWithEnumType.js index e694790091c4b..b87ab8298a725 100644 --- a/tests/baselines/reference/deleteOperatorWithEnumType.js +++ b/tests/baselines/reference/deleteOperatorWithEnumType.js @@ -39,11 +39,11 @@ var ENUM1; var ResultIsBoolean1 = delete ENUM; var ResultIsBoolean2 = delete ENUM1; // enum type expressions -var ResultIsBoolean3 = delete ENUM1["A"]; -var ResultIsBoolean4 = delete (ENUM[0] + ENUM1["B"]); +var ResultIsBoolean3 = delete 0 /* "A" */; +var ResultIsBoolean4 = delete (ENUM[0] + 1 /* "B" */); // multiple delete operators var ResultIsBoolean5 = delete delete ENUM; -var ResultIsBoolean6 = delete delete delete (ENUM[0] + ENUM1["B"]); +var ResultIsBoolean6 = delete delete delete (ENUM[0] + 1 /* "B" */); // miss assignment operators delete ENUM; delete ENUM1; diff --git a/tests/baselines/reference/incrementOperatorWithEnumType.js b/tests/baselines/reference/incrementOperatorWithEnumType.js index 2baa326f3997e..590815ffec55a 100644 --- a/tests/baselines/reference/incrementOperatorWithEnumType.js +++ b/tests/baselines/reference/incrementOperatorWithEnumType.js @@ -22,8 +22,8 @@ var ENUM1; })(ENUM1 || (ENUM1 = {})); ; // expression -var ResultIsNumber1 = ++ENUM1["B"]; +var ResultIsNumber1 = ++1 /* "B" */; var ResultIsNumber2 = 1 /* B */++; // miss assignment operator -++ENUM1["B"]; +++1 /* "B" */; 1 /* B */++; diff --git a/tests/baselines/reference/logicalNotOperatorWithEnumType.js b/tests/baselines/reference/logicalNotOperatorWithEnumType.js index 69ea283051de0..89ee1a148335a 100644 --- a/tests/baselines/reference/logicalNotOperatorWithEnumType.js +++ b/tests/baselines/reference/logicalNotOperatorWithEnumType.js @@ -37,11 +37,11 @@ var ENUM1; // enum type var var ResultIsBoolean1 = !ENUM; // enum type expressions -var ResultIsBoolean2 = !ENUM["B"]; -var ResultIsBoolean3 = !(1 /* B */ + ENUM["C"]); +var ResultIsBoolean2 = !1 /* "B" */; +var ResultIsBoolean3 = !(1 /* B */ + 2 /* "C" */); // multiple ! operators var ResultIsBoolean4 = !!ENUM; -var ResultIsBoolean5 = !!!(ENUM["B"] + 2 /* C */); +var ResultIsBoolean5 = !!!(1 /* "B" */ + 2 /* C */); // miss assignment operators !ENUM; !ENUM1; diff --git a/tests/baselines/reference/negateOperatorWithEnumType.js b/tests/baselines/reference/negateOperatorWithEnumType.js index 876bed45cc2be..77458f442df9a 100644 --- a/tests/baselines/reference/negateOperatorWithEnumType.js +++ b/tests/baselines/reference/negateOperatorWithEnumType.js @@ -33,10 +33,10 @@ var ENUM1; // enum type var var ResultIsNumber1 = -ENUM; // expressions -var ResultIsNumber2 = -ENUM1["B"]; -var ResultIsNumber3 = -(1 /* B */ + ENUM1[""]); +var ResultIsNumber2 = -1 /* "B" */; +var ResultIsNumber3 = -(1 /* B */ + 2 /* "" */); // miss assignment operators -ENUM; -ENUM1; --ENUM1["B"]; +-1 /* "B" */; -ENUM, ENUM1; diff --git a/tests/baselines/reference/noImplicitAnyIndexing.js b/tests/baselines/reference/noImplicitAnyIndexing.js index ebadbd9a12197..91dbcae730c93 100644 --- a/tests/baselines/reference/noImplicitAnyIndexing.js +++ b/tests/baselines/reference/noImplicitAnyIndexing.js @@ -61,7 +61,7 @@ var strRepresentation2 = MyEmusEnum[0 /* emu */]; // Should be implicit 'any' ; property access fails, no string indexer. var strRepresentation3 = MyEmusEnum["monehh"]; // Should be okay; should be a MyEmusEnum -var strRepresentation4 = MyEmusEnum["emu"]; +var strRepresentation4 = 0 /* "emu" */; // Should report an implicit 'any'. var x = {}["hi"]; // Should report an implicit 'any'. diff --git a/tests/baselines/reference/plusOperatorWithEnumType.js b/tests/baselines/reference/plusOperatorWithEnumType.js index 4d6eaad892ecd..1ce6036d5ce15 100644 --- a/tests/baselines/reference/plusOperatorWithEnumType.js +++ b/tests/baselines/reference/plusOperatorWithEnumType.js @@ -35,8 +35,8 @@ var ENUM1; var ResultIsNumber1 = +ENUM; var ResultIsNumber2 = +ENUM1; // enum type expressions -var ResultIsNumber3 = +ENUM1["A"]; -var ResultIsNumber4 = +(ENUM[0] + ENUM1["B"]); +var ResultIsNumber3 = +0 /* "A" */; +var ResultIsNumber4 = +(ENUM[0] + 1 /* "B" */); // miss assignment operators +ENUM; +ENUM1; diff --git a/tests/baselines/reference/typeofOperatorWithEnumType.js b/tests/baselines/reference/typeofOperatorWithEnumType.js index 272feacf63d5d..900b610cef463 100644 --- a/tests/baselines/reference/typeofOperatorWithEnumType.js +++ b/tests/baselines/reference/typeofOperatorWithEnumType.js @@ -44,15 +44,15 @@ var ENUM1; var ResultIsString1 = typeof ENUM; var ResultIsString2 = typeof ENUM1; // enum type expressions -var ResultIsString3 = typeof ENUM1["A"]; -var ResultIsString4 = typeof (ENUM[0] + ENUM1["B"]); +var ResultIsString3 = typeof 0 /* "A" */; +var ResultIsString4 = typeof (ENUM[0] + 1 /* "B" */); // multiple typeof operators var ResultIsString5 = typeof typeof ENUM; var ResultIsString6 = typeof typeof typeof (ENUM[0] + 1 /* B */); // miss assignment operators typeof ENUM; typeof ENUM1; -typeof ENUM1["B"]; +typeof 1 /* "B" */; typeof ENUM, ENUM1; // use typeof in type query var z; diff --git a/tests/baselines/reference/voidOperatorWithEnumType.js b/tests/baselines/reference/voidOperatorWithEnumType.js index 8d20842ea7b5c..478ac85651b4d 100644 --- a/tests/baselines/reference/voidOperatorWithEnumType.js +++ b/tests/baselines/reference/voidOperatorWithEnumType.js @@ -39,13 +39,13 @@ var ENUM1; var ResultIsAny1 = void ENUM; var ResultIsAny2 = void ENUM1; // enum type expressions -var ResultIsAny3 = void ENUM1["A"]; -var ResultIsAny4 = void (ENUM[0] + ENUM1["B"]); +var ResultIsAny3 = void 0 /* "A" */; +var ResultIsAny4 = void (ENUM[0] + 1 /* "B" */); // multiple void operators var ResultIsAny5 = void void ENUM; var ResultIsAny6 = void void void (ENUM[0] + 1 /* B */); // miss assignment operators void ENUM; void ENUM1; -void ENUM1["B"]; +void 1 /* "B" */; void ENUM, ENUM1; diff --git a/tests/cases/compiler/constEnums.ts b/tests/cases/compiler/constEnums.ts index 504ad3e5a7579..72b75a3f31ec9 100644 --- a/tests/cases/compiler/constEnums.ts +++ b/tests/cases/compiler/constEnums.ts @@ -130,7 +130,7 @@ function foo(x: Enum1) { case Enum1.Q: case Enum1.R: case Enum1.S: - case Enum1.T: + case Enum1["T"]: case Enum1.U: case Enum1.V: case Enum1.W: From 4d354c02864caf7f5dc4fde1b31c722c90b18637 Mon Sep 17 00:00:00 2001 From: vladima Date: Mon, 3 Nov 2014 11:13:32 -0800 Subject: [PATCH 16/16] addressed CR feedback: adjusted text of error messages, added description to 'preserveConstEnums' command line argument --- src/compiler/checker.ts | 6 ++--- src/compiler/commandLineParser.ts | 3 ++- .../diagnosticInformationMap.generated.ts | 7 +++--- src/compiler/diagnosticMessages.json | 10 +++++--- .../reference/constEnumErrors.errors.txt | 24 +++++++++---------- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 64746d75417fe..cac2d60d50034 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6261,7 +6261,7 @@ module ts { ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(node)); if (!ok) { - error(node, Diagnostics.const_enums_can_only_be_used_in_property_access_Slashindex_access_expressions_and_as_right_hand_side_in_import_declarations_Slashexport_assignments); + error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment); } } return type; @@ -7771,10 +7771,10 @@ module ts { } else if (enumIsConst) { if (isNaN(autoValue)) { - error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_NaN); + error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN); } else if (!isFinite(autoValue)) { - error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_number); + error(initializer, Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value); } } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 47520cb031771..fed7c991b2e3d 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -121,7 +121,8 @@ module ts { }, { name: "preserveConstEnums", - type: "boolean" + type: "boolean", + description: Diagnostics.Do_not_erase_const_enum_declarations_in_generated_code } ]; diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index a868133044fa1..9138baeda4117 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -354,10 +354,10 @@ module ts { Exported_type_alias_0_has_or_is_using_private_name_1: { code: 4081, category: DiagnosticCategory.Error, key: "Exported type alias '{0}' has or is using private name '{1}'." }, Enum_declarations_must_all_be_const_or_non_const: { code: 4082, category: DiagnosticCategory.Error, key: "Enum declarations must all be const or non-const." }, 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.", isEarly: true }, - const_enums_can_only_be_used_in_property_access_Slashindex_access_expressions_and_as_right_hand_side_in_import_declarations_Slashexport_assignments: { code: 4084, category: DiagnosticCategory.Error, key: "'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments." }, + const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment: { code: 4084, category: DiagnosticCategory.Error, key: "'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment." }, 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'." }, - const_enum_member_initializer_was_evaluated_to_a_non_finite_number: { code: 4086, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to a non-finite number." }, - const_enum_member_initializer_was_evaluated_to_NaN: { code: 4087, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to NaN." }, + const_enum_member_initializer_was_evaluated_to_a_non_finite_value: { code: 4086, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to a non-finite value." }, + const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN: { code: 4087, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to disallowed value 'NaN'." }, 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}" }, @@ -372,6 +372,7 @@ module ts { Specifies_the_location_where_debugger_should_locate_TypeScript_files_instead_of_source_locations: { code: 6004, category: DiagnosticCategory.Message, key: "Specifies the location where debugger should locate TypeScript files instead of source locations." }, Watch_input_files: { code: 6005, category: DiagnosticCategory.Message, key: "Watch input files." }, Redirect_output_structure_to_the_directory: { code: 6006, category: DiagnosticCategory.Message, key: "Redirect output structure to the directory." }, + Do_not_erase_const_enum_declarations_in_generated_code: { code: 6007, category: DiagnosticCategory.Message, key: "Do not erase const enum declarations in generated code." }, Do_not_emit_comments_to_output: { code: 6009, category: DiagnosticCategory.Message, key: "Do not emit comments to output." }, Specify_ECMAScript_target_version_Colon_ES3_default_ES5_or_ES6_experimental: { code: 6015, category: DiagnosticCategory.Message, key: "Specify ECMAScript target version: 'ES3' (default), 'ES5', or 'ES6' (experimental)" }, Specify_module_code_generation_Colon_commonjs_or_amd: { code: 6016, category: DiagnosticCategory.Message, key: "Specify module code generation: 'commonjs' or 'amd'" }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index eafde362f962f..b6c9df1cf3666 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1418,7 +1418,7 @@ "code": 4083, "isEarly": true }, - "'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments.": { + "'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment.": { "category": "Error", "code": 4084 }, @@ -1426,11 +1426,11 @@ "category": "Error", "code": 4085 }, - "'const' enum member initializer was evaluated to a non-finite number.": { + "'const' enum member initializer was evaluated to a non-finite value.": { "category": "Error", "code": 4086 }, - "'const' enum member initializer was evaluated to NaN.": { + "'const' enum member initializer was evaluated to disallowed value 'NaN'.": { "category": "Error", "code": 4087 }, @@ -1490,6 +1490,10 @@ "category": "Message", "code": 6006 }, + "Do not erase const enum declarations in generated code.": { + "category": "Message", + "code": 6007 + }, "Do not emit comments to output.": { "category": "Message", "code": 6009 diff --git a/tests/baselines/reference/constEnumErrors.errors.txt b/tests/baselines/reference/constEnumErrors.errors.txt index 78115a22dc44c..541f814af983d 100644 --- a/tests/baselines/reference/constEnumErrors.errors.txt +++ b/tests/baselines/reference/constEnumErrors.errors.txt @@ -5,12 +5,12 @@ tests/cases/compiler/constEnumErrors.ts(14,9): error TS4083: In 'const' enum dec tests/cases/compiler/constEnumErrors.ts(15,10): error TS4083: In 'const' enum declarations member initializer must be constant expression. tests/cases/compiler/constEnumErrors.ts(22,13): error TS4085: Index expression arguments in 'const' enums must be of type 'string'. tests/cases/compiler/constEnumErrors.ts(24,13): error TS4085: Index expression arguments in 'const' enums must be of type 'string'. -tests/cases/compiler/constEnumErrors.ts(26,9): error TS4084: 'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments. -tests/cases/compiler/constEnumErrors.ts(27,10): error TS4084: 'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments. -tests/cases/compiler/constEnumErrors.ts(32,5): error TS4084: 'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments. -tests/cases/compiler/constEnumErrors.ts(40,9): error TS4086: 'const' enum member initializer was evaluated to a non-finite number. -tests/cases/compiler/constEnumErrors.ts(41,9): error TS4086: 'const' enum member initializer was evaluated to a non-finite number. -tests/cases/compiler/constEnumErrors.ts(42,9): error TS4087: 'const' enum member initializer was evaluated to NaN. +tests/cases/compiler/constEnumErrors.ts(26,9): error TS4084: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment. +tests/cases/compiler/constEnumErrors.ts(27,10): error TS4084: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment. +tests/cases/compiler/constEnumErrors.ts(32,5): error TS4084: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment. +tests/cases/compiler/constEnumErrors.ts(40,9): error TS4086: 'const' enum member initializer was evaluated to a non-finite value. +tests/cases/compiler/constEnumErrors.ts(41,9): error TS4086: 'const' enum member initializer was evaluated to a non-finite value. +tests/cases/compiler/constEnumErrors.ts(42,9): error TS4087: 'const' enum member initializer was evaluated to disallowed value 'NaN'. ==== tests/cases/compiler/constEnumErrors.ts (13 errors) ==== @@ -55,17 +55,17 @@ tests/cases/compiler/constEnumErrors.ts(42,9): error TS4087: 'const' enum member var x = E2; ~~ -!!! error TS4084: 'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments. +!!! error TS4084: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment. var y = [E2]; ~~ -!!! error TS4084: 'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments. +!!! error TS4084: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment. function foo(t: any): void { } foo(E2); ~~ -!!! error TS4084: 'const' enums can only be used in property access/index access expressions and as right hand side in import declarations/export assignments. +!!! error TS4084: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment. const enum NaNOrInfinity { A = 9007199254740992, @@ -75,11 +75,11 @@ tests/cases/compiler/constEnumErrors.ts(42,9): error TS4087: 'const' enum member E = D * D, F = E * E, // overflow ~~~~~ -!!! error TS4086: 'const' enum member initializer was evaluated to a non-finite number. +!!! error TS4086: 'const' enum member initializer was evaluated to a non-finite value. G = 1 / 0, // overflow ~~~~~ -!!! error TS4086: 'const' enum member initializer was evaluated to a non-finite number. +!!! error TS4086: 'const' enum member initializer was evaluated to a non-finite value. H = 0 / 0 // NaN ~~~~~ -!!! error TS4087: 'const' enum member initializer was evaluated to NaN. +!!! error TS4087: 'const' enum member initializer was evaluated to disallowed value 'NaN'. } \ No newline at end of file