Skip to content

Error for merging multiple enums without initial initializers #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 69 additions & 26 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,13 @@ module ts {
}

function getExportAssignmentSymbol(symbol: Symbol): Symbol {
if (!symbol.exportAssignSymbol) {
var exportInformation = collectExportInformationForSourceFileOrModule(symbol);
checkAndStoreTypeOfExportAssignmentSymbol(symbol);
return symbol.exportAssignSymbol === unknownSymbol ? undefined : symbol.exportAssignSymbol;
}

function checkAndStoreTypeOfExportAssignmentSymbol(containerSymbol: Symbol): void {
if (!containerSymbol.exportAssignSymbol) {
var exportInformation = collectExportInformationForSourceFileOrModule(containerSymbol);
if (exportInformation.exportAssignments.length) {
if (exportInformation.exportAssignments.length > 1) {
// TypeScript 1.0 spec (April 2014): 11.2.4
Expand All @@ -436,9 +441,8 @@ module ts {
var exportSymbol = resolveName(node, node.exportName.text, meaning, Diagnostics.Cannot_find_name_0, identifierToString(node.exportName));
}
}
symbol.exportAssignSymbol = exportSymbol || unknownSymbol;
containerSymbol.exportAssignSymbol = exportSymbol || unknownSymbol;
}
return symbol.exportAssignSymbol === unknownSymbol ? undefined : symbol.exportAssignSymbol;
}

function collectExportInformationForSourceFileOrModule(symbol: Symbol) {
Expand Down Expand Up @@ -504,8 +508,12 @@ module ts {
var declarations = symbol.declarations;
for (var i = 0; i < declarations.length; i++) {
var declaration = declarations[i];
if (declaration.kind === kind) return declaration;
if (declaration.kind === kind) {
return declaration;
}
}

return undefined;
}

function findConstructorDeclaration(node: ClassDeclaration): ConstructorDeclaration {
Expand Down Expand Up @@ -922,6 +930,12 @@ module ts {

function getTypeOfAccessors(symbol: Symbol): Type {
var links = getSymbolLinks(symbol);
checkAndStoreTypeOfAccessors(symbol, links);
return links.type;
}

function checkAndStoreTypeOfAccessors(symbol: Symbol, links?: SymbolLinks) {
links = links || getSymbolLinks(symbol);
if (!links.type) {
links.type = resolvingType;
var getter = <AccessorDeclaration>getDeclarationOfKind(symbol, SyntaxKind.GetAccessor);
Expand Down Expand Up @@ -955,7 +969,6 @@ module ts {
}
}
}


if (links.type === resolvingType) {
links.type = type;
Expand All @@ -964,7 +977,6 @@ module ts {
else if (links.type === resolvingType) {
links.type = anyType;
}
return links.type;
}

function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
Expand Down Expand Up @@ -4212,11 +4224,10 @@ module ts {
checkSourceElement(node.body);

var symbol = getSymbolOfNode(node);
var symbolLinks = getSymbolLinks(symbol);
var type = getTypeOfSymbol(symbol.parent);
if (!(symbolLinks.typeChecked)) {
var firstDeclaration = getDeclarationOfKind(symbol, node.kind);
// Only type check the symbol once
if (node === firstDeclaration) {
checkFunctionOrConstructorSymbol(symbol);
symbolLinks.typeChecked = true;
}

// exit early in the case of signature - super checks are not relevant to them
Expand Down Expand Up @@ -4335,6 +4346,7 @@ module ts {
}

checkFunctionDeclaration(node);
checkAndStoreTypeOfAccessors(getSymbolOfNode(node));
}

function checkTypeReference(node: TypeReferenceNode) {
Expand Down Expand Up @@ -4550,12 +4562,12 @@ module ts {
function checkFunctionDeclaration(node: FunctionDeclaration) {
checkDeclarationModifiers(node);
checkSignatureDeclaration(node);

var symbol = getSymbolOfNode(node);
var symbolLinks = getSymbolLinks(symbol);
var type = getTypeOfSymbol(symbol);
if (!(symbolLinks.typeChecked)) {
var firstDeclaration = getDeclarationOfKind(symbol, node.kind);
// Only type check the symbol once
if (node === firstDeclaration) {
checkFunctionOrConstructorSymbol(symbol);
symbolLinks.typeChecked = true;
}
checkSourceElement(node.body);

Expand Down Expand Up @@ -5156,15 +5168,15 @@ module ts {
checkNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0);
checkTypeParameters(node.typeParameters);
var symbol = getSymbolOfNode(node);
var firstInterfaceDecl = <InterfaceDeclaration>getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration);
if (symbol.declarations.length > 1) {
var firstInterfaceDecl = <InterfaceDeclaration>getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration);
if (node !== firstInterfaceDecl && !areTypeParametersIdentical(firstInterfaceDecl.typeParameters, node.typeParameters)) {
error(node.name, Diagnostics.All_declarations_of_an_interface_must_have_identical_type_parameters);
}
}

var links = getSymbolLinks(symbol);
if (!links.typeChecked) {
// Only check this symbol once
if (node === firstInterfaceDecl) {
var type = <InterfaceType>getDeclaredTypeOfSymbol(symbol);
// run subsequent checks only if first set succeeded
if (checkInheritedPropertiesAreIdentical(type, node.name)) {
Expand All @@ -5173,7 +5185,6 @@ module ts {
});
checkIndexConstraints(type);
}
links.typeChecked = true;
}
forEach(node.baseTypes, checkTypeReference);
forEach(node.members, checkSourceElement);
Expand Down Expand Up @@ -5201,7 +5212,8 @@ module ts {
function checkEnumDeclaration(node: EnumDeclaration) {
checkDeclarationModifiers(node);
checkNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0);
var enumType = getDeclaredTypeOfSymbol(getSymbolOfNode(node));
var enumSymbol = getSymbolOfNode(node);
var enumType = getDeclaredTypeOfSymbol(enumSymbol);
var autoValue = 0;
var ambient = isInAmbientContext(node);
forEach(node.members, member => {
Expand All @@ -5224,7 +5236,38 @@ module ts {
getNodeLinks(member).enumMemberValue = autoValue++;
}
});
// TODO: Only one enum declaration can omit the initial value

// Spec 2014 - Section 9.3:
// It isn't possible for one enum declaration to continue the automatic numbering sequence of another,
// and when an enum type has multiple declarations, only one declaration is permitted to omit a value
// for the first member.
//
// Only perform this check once per symbol
var firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind);
if (node === firstDeclaration) {
var seenEnumMissingInitialInitializer = false;
forEach(enumSymbol.declarations, declaration => {
// return true if we hit a violation of the rule, false otherwise
if (declaration.kind !== SyntaxKind.EnumDeclaration) {
return false;
}

var enumDeclaration = <EnumDeclaration>declaration;
if (!enumDeclaration.members.length) {
return false;
}

var firstEnumMember = enumDeclaration.members[0];
if (!firstEnumMember.initializer) {
if (seenEnumMissingInitialInitializer) {
error(firstEnumMember.name, Diagnostics.In_an_enum_with_multiple_declarations_only_one_declaration_can_omit_an_initializer_for_its_first_enum_element);
}
else {
seenEnumMissingInitialInitializer = true;
}
}
});
}
}

function checkModuleDeclaration(node: ModuleDeclaration) {
Expand All @@ -5237,11 +5280,6 @@ module ts {
error(node, Diagnostics.Ambient_external_module_declaration_cannot_specify_relative_module_name);
}
var symbol = getSymbolOfNode(node);
var links = getSymbolLinks(symbol);
if (!links.typeChecked) {
getExportAssignmentSymbol(symbol);
links.typeChecked = true;
}
}
checkSourceElement(node.body);
}
Expand Down Expand Up @@ -5310,6 +5348,11 @@ module ts {
if (container.kind === SyntaxKind.SourceFile) {
checkModulesEnabled(node);
}
else {
// In a module, the immediate parent will be a block, so climb up one more parent
container = container.parent;
}
checkAndStoreTypeOfExportAssignmentSymbol(getSymbolOfNode(container));
}

function checkSourceElement(node: Node): void {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ module ts {
Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function: { code: 4017, category: DiagnosticCategory.NoPrefix, key: "Class '{0}' defines instance member property '{1}', but extended class '{2}' defines it as instance member function." },
Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor: { code: 4018, category: DiagnosticCategory.NoPrefix, key: "Class '{0}' defines instance member function '{1}', but extended class '{2}' defines it as instance member accessor." },
Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_property: { code: 4019, category: DiagnosticCategory.NoPrefix, key: "Class '{0}' defines instance member function '{1}', but extended class '{2}' defines it as instance member property." },
In_an_enum_with_multiple_declarations_only_one_declaration_can_omit_an_initializer_for_its_first_enum_element: { code: 4024, category: DiagnosticCategory.Error, key: "In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element." },
Named_properties_0_of_types_1_and_2_are_not_identical: { code: 4032, category: DiagnosticCategory.NoPrefix, key: "Named properties '{0}' of types '{1}' and '{2}' are not identical." },
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}" },
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,10 @@
"category": "NoPrefix",
"code": 4019
},
"In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.": {
"category": "Error",
"code": 4024
},
"Named properties '{0}' of types '{1}' and '{2}' are not identical.": {
"category": "NoPrefix",
"code": 4032
Expand Down
1 change: 0 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,6 @@ module ts {
type?: Type; // Type of value symbol
declaredType?: Type; // Type of class, interface, enum, or type parameter
mapper?: TypeMapper; // Type mapper for instantiation alias
typeChecked?: boolean; // True if symbol has been type checked
referenced?: boolean; // True if alias symbol has been referenced as a value
}

Expand Down
6 changes: 5 additions & 1 deletion tests/baselines/reference/augmentedTypesEnum.errors.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
==== tests/cases/compiler/augmentedTypesEnum.ts (5 errors) ====
==== tests/cases/compiler/augmentedTypesEnum.ts (7 errors) ====
// enum then var
enum e1111 { One }
var e1111 = 1; // error
Expand All @@ -25,11 +25,15 @@
// enum then enum
enum e5 { One }
enum e5 { Two }
~~~
!!! In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.

enum e5a { One }
enum e5a { One } // error
~~~
!!! Duplicate identifier 'One'.
~~~
!!! In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.

// enum then internal module
enum e6 { One }
Expand Down
23 changes: 23 additions & 0 deletions tests/baselines/reference/augmentedTypesEnum3.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
==== tests/cases/compiler/augmentedTypesEnum3.ts (1 errors) ====
module E {
var t;
}
enum E { }

enum F { }
module F { var t; }

module A {
var o;
}
enum A {
b
}
enum A {
c
~
!!! In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.
}
module A {
var p;
}
6 changes: 4 additions & 2 deletions tests/baselines/reference/enumBasics1.errors.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
==== tests/cases/compiler/enumBasics1.ts (1 errors) ====
==== tests/cases/compiler/enumBasics1.ts (2 errors) ====
enum E {
A = 1,
B,
Expand Down Expand Up @@ -34,7 +34,9 @@
B,
}

enum E2 { // shouldn't error
enum E2 { // should error for continued autonumbering
C,
~
!!! In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.
D,
}
2 changes: 1 addition & 1 deletion tests/baselines/reference/enumBasics1.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ enum E2 {
B,
}

enum E2 { // shouldn't error
enum E2 { // should error for continued autonumbering
C,
D,
}
Expand Down
47 changes: 47 additions & 0 deletions tests/baselines/reference/enumMergingErrors.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
==== tests/cases/conformance/enums/enumMergingErrors.ts (2 errors) ====
// Enum with constant, computed, constant members split across 3 declarations with the same root module
module M {
export enum E1 { A = 0 }
export enum E2 { C }
export enum E3 { A = 0 }
}
module M {
export enum E1 { B = 'foo'.length }
export enum E2 { B = 'foo'.length }
export enum E3 { C }
}
module M {
export enum E1 { C }
export enum E2 { A = 0 }
export enum E3 { B = 'foo'.length }
}

// Enum with no initializer in either declaration with constant members with the same root module
module M1 {
export enum E1 { A = 0 }
}
module M1 {
export enum E1 { B }
}
module M1 {
export enum E1 { C }
~
!!! In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.
}


// Enum with initializer in only one of three declarations with constant members with the same root module
module M2 {
export enum E1 { A }
}
module M2 {
export enum E1 { B = 0 }
}
module M2 {
export enum E1 { C }
~
!!! In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
==== tests/cases/compiler/enumsWithMultipleDeclarations1.ts (2 errors) ====
enum E {
A
}

enum E {
B
~
!!! In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.
}

enum E {
C
~
!!! In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
==== tests/cases/compiler/enumsWithMultipleDeclarations2.ts (1 errors) ====
enum E {
A
}

enum E {
B = 1
}

enum E {
C
~
!!! In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.
}
Loading