diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 665213dd6648d..e0094099a1e5c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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 @@ -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) { @@ -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 { @@ -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 = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); @@ -955,7 +969,6 @@ module ts { } } } - if (links.type === resolvingType) { links.type = type; @@ -964,7 +977,6 @@ module ts { else if (links.type === resolvingType) { links.type = anyType; } - return links.type; } function getTypeOfFuncClassEnumModule(symbol: Symbol): Type { @@ -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 @@ -4335,6 +4346,7 @@ module ts { } checkFunctionDeclaration(node); + checkAndStoreTypeOfAccessors(getSymbolOfNode(node)); } function checkTypeReference(node: TypeReferenceNode) { @@ -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); @@ -5156,15 +5168,15 @@ module ts { checkNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); checkTypeParameters(node.typeParameters); var symbol = getSymbolOfNode(node); + var firstInterfaceDecl = getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration); if (symbol.declarations.length > 1) { - var firstInterfaceDecl = 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 = getDeclaredTypeOfSymbol(symbol); // run subsequent checks only if first set succeeded if (checkInheritedPropertiesAreIdentical(type, node.name)) { @@ -5173,7 +5185,6 @@ module ts { }); checkIndexConstraints(type); } - links.typeChecked = true; } forEach(node.baseTypes, checkTypeReference); forEach(node.members, checkSourceElement); @@ -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 => { @@ -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 = 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) { @@ -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); } @@ -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 { diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index 2ca3f70966ec5..d8eb2cb51efad 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -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}" }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 0e40ea4b04953..58854b56e4ae1 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -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 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ef9a39dfe28b0..d635091b25ae1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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 } diff --git a/tests/baselines/reference/augmentedTypesEnum.errors.txt b/tests/baselines/reference/augmentedTypesEnum.errors.txt index 557c266f830f2..d888f170b5d70 100644 --- a/tests/baselines/reference/augmentedTypesEnum.errors.txt +++ b/tests/baselines/reference/augmentedTypesEnum.errors.txt @@ -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 @@ -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 } diff --git a/tests/baselines/reference/augmentedTypesEnum3.errors.txt b/tests/baselines/reference/augmentedTypesEnum3.errors.txt new file mode 100644 index 0000000000000..c9e7bca61d42c --- /dev/null +++ b/tests/baselines/reference/augmentedTypesEnum3.errors.txt @@ -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; + } \ No newline at end of file diff --git a/tests/baselines/reference/enumBasics1.errors.txt b/tests/baselines/reference/enumBasics1.errors.txt index 8f193634fede4..6d9229341e2b0 100644 --- a/tests/baselines/reference/enumBasics1.errors.txt +++ b/tests/baselines/reference/enumBasics1.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/compiler/enumBasics1.ts (1 errors) ==== +==== tests/cases/compiler/enumBasics1.ts (2 errors) ==== enum E { A = 1, B, @@ -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, } \ No newline at end of file diff --git a/tests/baselines/reference/enumBasics1.js b/tests/baselines/reference/enumBasics1.js index 3d3253ea92cae..c8549fe48050e 100644 --- a/tests/baselines/reference/enumBasics1.js +++ b/tests/baselines/reference/enumBasics1.js @@ -32,7 +32,7 @@ enum E2 { B, } -enum E2 { // shouldn't error +enum E2 { // should error for continued autonumbering C, D, } diff --git a/tests/baselines/reference/enumMergingErrors.errors.txt b/tests/baselines/reference/enumMergingErrors.errors.txt new file mode 100644 index 0000000000000..30cf6ef18cd14 --- /dev/null +++ b/tests/baselines/reference/enumMergingErrors.errors.txt @@ -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. + } + + + \ No newline at end of file diff --git a/tests/baselines/reference/enumsWithMultipleDeclarations1.errors.txt b/tests/baselines/reference/enumsWithMultipleDeclarations1.errors.txt new file mode 100644 index 0000000000000..d2336e905c595 --- /dev/null +++ b/tests/baselines/reference/enumsWithMultipleDeclarations1.errors.txt @@ -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. + } \ No newline at end of file diff --git a/tests/baselines/reference/enumsWithMultipleDeclarations2.errors.txt b/tests/baselines/reference/enumsWithMultipleDeclarations2.errors.txt new file mode 100644 index 0000000000000..ef7040dd94617 --- /dev/null +++ b/tests/baselines/reference/enumsWithMultipleDeclarations2.errors.txt @@ -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. + } \ No newline at end of file diff --git a/tests/baselines/reference/functionAndInterfaceWithSeparateErrors.errors.txt b/tests/baselines/reference/functionAndInterfaceWithSeparateErrors.errors.txt new file mode 100644 index 0000000000000..58936d29dbb2a --- /dev/null +++ b/tests/baselines/reference/functionAndInterfaceWithSeparateErrors.errors.txt @@ -0,0 +1,12 @@ +==== tests/cases/compiler/functionAndInterfaceWithSeparateErrors.ts (2 errors) ==== + function Foo(s: string); + ~~~~~~~~~~~~~~~~~~~~~~~~ +!!! Overload signature is not compatible with function implementation. + function Foo(n: number) { } + + interface Foo { + [s: string]: string; + prop: number; + ~~~~~~~~~~~~~ +!!! Property 'prop' of type 'number' is not assignable to string index type 'string'. + } \ No newline at end of file diff --git a/tests/baselines/reference/functionAndInterfaceWithSeparateErrors.js b/tests/baselines/reference/functionAndInterfaceWithSeparateErrors.js new file mode 100644 index 0000000000000..004e65d92afb1 --- /dev/null +++ b/tests/baselines/reference/functionAndInterfaceWithSeparateErrors.js @@ -0,0 +1,12 @@ +//// [functionAndInterfaceWithSeparateErrors.ts] +function Foo(s: string); +function Foo(n: number) { } + +interface Foo { + [s: string]: string; + prop: number; +} + +//// [functionAndInterfaceWithSeparateErrors.js] +function Foo(n) { +} diff --git a/tests/baselines/reference/parserExportAssignment5.errors.txt b/tests/baselines/reference/parserExportAssignment5.errors.txt index 01cd8392c059b..c72673ef86528 100644 --- a/tests/baselines/reference/parserExportAssignment5.errors.txt +++ b/tests/baselines/reference/parserExportAssignment5.errors.txt @@ -1,6 +1,8 @@ -==== tests/cases/conformance/parser/ecmascript5/ExportAssignments/parserExportAssignment5.ts (1 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ExportAssignments/parserExportAssignment5.ts (2 errors) ==== module M { export = A; ~~~~~~~~~~~ !!! An export assignment cannot be used in an internal module. + ~~~~~~~~~~~ +!!! Cannot find name 'A'. } \ No newline at end of file diff --git a/tests/cases/compiler/enumBasics1.ts b/tests/cases/compiler/enumBasics1.ts index 5401cc777797f..b3614d5bcc2b9 100644 --- a/tests/cases/compiler/enumBasics1.ts +++ b/tests/cases/compiler/enumBasics1.ts @@ -31,7 +31,7 @@ enum E2 { B, } -enum E2 { // shouldn't error +enum E2 { // should error for continued autonumbering C, D, } \ No newline at end of file diff --git a/tests/cases/compiler/functionAndInterfaceWithSeparateErrors.ts b/tests/cases/compiler/functionAndInterfaceWithSeparateErrors.ts new file mode 100644 index 0000000000000..771e0488450c4 --- /dev/null +++ b/tests/cases/compiler/functionAndInterfaceWithSeparateErrors.ts @@ -0,0 +1,7 @@ +function Foo(s: string); +function Foo(n: number) { } + +interface Foo { + [s: string]: string; + prop: number; +} \ No newline at end of file