diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 91edaf7c920..014fba97388 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 a6fc5b2ca69..c4bd95b46d5 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 c44de37649b..52a1070ebe4 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 1a84cd2fb89..8f3e5bd39e2 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