diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ba1005d01bd..30cbb800bc6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -90,7 +90,8 @@ module ts { getAugmentedPropertiesOfApparentType: getAugmentedPropertiesOfApparentType, getRootSymbol: getRootSymbol, getContextualType: getContextualType, - getFullyQualifiedName: getFullyQualifiedName + getFullyQualifiedName: getFullyQualifiedName, + getEnumMemberValue: getEnumMemberValue }; var undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined"); @@ -6495,7 +6496,7 @@ module ts { } } - function getConstantValue(node: Expression): number { + function getConstantValueForExpression(node: Expression): number { var isNegative = false; if (node.kind === SyntaxKind.PrefixOperator) { var unaryExpression = node; @@ -6512,38 +6513,55 @@ module ts { return undefined; } + function computeEnumMemberValues(node: EnumDeclaration, reportErrors: boolean) { + var nodeLinks = getNodeLinks(node); + + if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputedAndChecked)) { + var enumSymbol = getSymbolOfNode(node); + var enumType = getDeclaredTypeOfSymbol(enumSymbol); + var autoValue = 0; + var ambient = isInAmbientContext(node); + + forEach(node.members, member => { + var initializer = member.initializer; + if (initializer) { + autoValue = getConstantValueForExpression(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. + if (reportErrors) { + checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined); + } + } + } + else if (ambient) { + autoValue = undefined; + } + + if (autoValue !== undefined) { + getNodeLinks(member).enumMemberValue = autoValue++; + } + }); + + if (reportErrors) { + nodeLinks.flags |= NodeCheckFlags.EnumValuesComputedAndChecked; + } + } + } + function checkEnumDeclaration(node: EnumDeclaration) { if (!fullTypeCheck) { return; } + checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0); checkCollisionWithCapturedThisVariable(node, node.name); checkCollistionWithRequireExportsInGeneratedCode(node, node.name); checkExportsOnMergedDeclarations(node); - var enumSymbol = getSymbolOfNode(node); - var enumType = getDeclaredTypeOfSymbol(enumSymbol); - var autoValue = 0; - var ambient = isInAmbientContext(node); - forEach(node.members, member => { - var initializer = member.initializer; - if (initializer) { - autoValue = getConstantValue(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); - } - } - else if (ambient) { - autoValue = undefined; - } - if (autoValue !== undefined) { - getNodeLinks(member).enumMemberValue = autoValue++; - } - }); + computeEnumMemberValues(node, /*reportErrors:*/ true); // Spec 2014 - Section 9.3: // It isn't possible for one enum declaration to continue the automatic numbering sequence of another, @@ -6551,6 +6569,7 @@ module ts { // for the first member. // // Only perform this check once per symbol + var enumSymbol = getSymbolOfNode(node); var firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind); if (node === firstDeclaration) { var seenEnumMissingInitialInitializer = false; @@ -7450,17 +7469,6 @@ module ts { } } - function getPropertyAccessSubstitution(node: PropertyAccess): string { - var symbol = getNodeLinks(node).resolvedSymbol; - if (symbol && (symbol.flags & SymbolFlags.EnumMember)) { - var declaration = symbol.valueDeclaration; - var constantValue: number; - if (declaration.kind === SyntaxKind.EnumMember && (constantValue = getNodeLinks(declaration).enumMemberValue) !== undefined) { - return constantValue.toString() + " /* " + identifierToString(declaration.name) + " */"; - } - } - } - function getExportAssignmentName(node: SourceFile): string { var symbol = getExportAssignmentSymbol(getSymbolOfNode(node)); return symbol && symbolIsValue(symbol) ? symbolToString(symbol): undefined; @@ -7523,9 +7531,23 @@ module ts { } function getEnumMemberValue(node: EnumMember): number { + computeEnumMemberValues(node.parent, /*reportErrors:*/ false); return getNodeLinks(node).enumMemberValue; } + function getConstantValue(node: PropertyAccess): number { + var symbol = getNodeLinks(node).resolvedSymbol; + if (symbol && (symbol.flags & SymbolFlags.EnumMember)) { + var declaration = symbol.valueDeclaration; + var constantValue: number; + if (declaration.kind === SyntaxKind.EnumMember && (constantValue = getNodeLinks(declaration).enumMemberValue) !== undefined) { + return constantValue; + } + } + + return undefined; + } + // Create a single instance that we can wrap the underlying emitter TextWriter with. That // way we don't have to allocate a new wrapper every time writeTypeAtLocation and // writeReturnTypeOfSignatureDeclaration are called. @@ -7561,7 +7583,6 @@ module ts { getProgram: () => program, getLocalNameOfContainer: getLocalNameOfContainer, getExpressionNamePrefix: getExpressionNamePrefix, - getPropertyAccessSubstitution: getPropertyAccessSubstitution, getExportAssignmentName: getExportAssignmentName, isReferencedImportDeclaration: isReferencedImportDeclaration, getNodeCheckFlags: getNodeCheckFlags, @@ -7573,7 +7594,8 @@ module ts { writeTypeAtLocation: writeTypeAtLocation, writeReturnTypeOfSignatureDeclaration: writeReturnTypeOfSignatureDeclaration, isSymbolAccessible: isSymbolAccessible, - isImportDeclarationEntityNameReferenceDeclarationVisibile: isImportDeclarationEntityNameReferenceDeclarationVisibile + isImportDeclarationEntityNameReferenceDeclarationVisibile: isImportDeclarationEntityNameReferenceDeclarationVisibile, + getConstantValue: getConstantValue, }; checkProgram(); return emitFiles(resolver, targetSourceFile); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index b9229573472..663e15b2e3f 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -868,14 +868,15 @@ module ts { } function emitPropertyAccess(node: PropertyAccess) { - var text = resolver.getPropertyAccessSubstitution(node); - if (text) { - write(text); - return; + var constantValue = resolver.getConstantValue(node); + if (constantValue !== undefined) { + write(constantValue.toString() + " /* " + identifierToString(node.right) + " */"); + } + else { + emit(node.left); + write("."); + emit(node.right); } - emit(node.left); - write("."); - emit(node.right); } function emitIndexedAccess(node: IndexedAccess) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 09556ffc8de..5739778917e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -645,6 +645,10 @@ module ts { getAugmentedPropertiesOfApparentType(type: Type): Symbol[]; getRootSymbol(symbol: Symbol): Symbol; getContextualType(node: Node): Type; + + // Returns the constant value of this enum member, or 'undefined' if the enum member has a + // computed value. + getEnumMemberValue(node: EnumMember): number; } export interface TextWriter { @@ -680,7 +684,6 @@ module ts { getProgram(): Program; getLocalNameOfContainer(container: Declaration): string; getExpressionNamePrefix(node: Identifier): string; - getPropertyAccessSubstitution(node: PropertyAccess): string; getExportAssignmentName(node: SourceFile): string; isReferencedImportDeclaration(node: ImportDeclaration): boolean; isTopLevelValueImportedViaEntityName(node: ImportDeclaration): boolean; @@ -693,6 +696,10 @@ module ts { writeReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: TextWriter): void; isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags): SymbolAccessiblityResult; isImportDeclarationEntityNameReferenceDeclarationVisibile(entityName: EntityName): SymbolAccessiblityResult; + + // Returns the constant value this property access resolves to, or 'undefined' if it does + // resolve to a constant. + getConstantValue(node: PropertyAccess): number; } export enum SymbolFlags { @@ -794,13 +801,16 @@ module ts { } export enum NodeCheckFlags { - TypeChecked = 0x00000001, // Node has been type checked - LexicalThis = 0x00000002, // Lexical 'this' reference - CaptureThis = 0x00000004, // Lexical 'this' used in body - EmitExtends = 0x00000008, // Emit __extends - SuperInstance = 0x00000010, // Instance 'super' reference - SuperStatic = 0x00000020, // Static 'super' reference - ContextChecked = 0x00000040, // Contextual types have been assigned + TypeChecked = 0x00000001, // Node has been type checked + LexicalThis = 0x00000002, // Lexical 'this' reference + CaptureThis = 0x00000004, // Lexical 'this' used in body + EmitExtends = 0x00000008, // Emit __extends + SuperInstance = 0x00000010, // Instance 'super' reference + SuperStatic = 0x00000020, // Static 'super' reference + ContextChecked = 0x00000040, // Contextual types have been assigned + + // Values for enum members have been computed, and any errors have been reported for them. + EnumValuesComputedAndChecked = 0x00000080, } export interface NodeLinks { diff --git a/src/services/services.ts b/src/services/services.ts index 4a0f3d3e2ad..5d1fd8deb3d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2304,12 +2304,14 @@ module ts { totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, getContainerNode(node))); if (symbol.flags & SymbolFlags.Property || - symbol.flags & SymbolFlags.EnumMember || symbol.flags & SymbolFlags.Variable) { totalParts.push({ text: ":", kind: SymbolDisplayPartKind.punctuation, symbol: undefined }); totalParts.push({ text: " ", kind: SymbolDisplayPartKind.space, symbol: undefined }); } + else if (symbol.flags & SymbolFlags.EnumMember) { + + } if (addType) { var type = typeInfoResolver.getTypeOfSymbol(symbol);