diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index bbb78bd2e58..82fdaf5cdcd 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 efb99cac8a1..c64da385e9c 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 e466292897a..5c05e7b30f9 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 5292a61ec27..ae7314d625f 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 b0457a82c71..3a82e71c677 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 67a59778dfc..d74ba6375b6 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 aa19b8e9230..6792c4f8bea 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 3ce0eeac9e3..6197d61dc79 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 3198b25d51f..504ad3e5a75 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: