Skip to content

Commit fae7a35

Browse files
Provide capability to ask the checker what the constant value of an enum member is.
1 parent 89eff56 commit fae7a35

File tree

4 files changed

+89
-54
lines changed

4 files changed

+89
-54
lines changed

src/compiler/checker.ts

Lines changed: 60 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ module ts {
9090
getAugmentedPropertiesOfApparentType: getAugmentedPropertiesOfApparentType,
9191
getRootSymbol: getRootSymbol,
9292
getContextualType: getContextualType,
93-
getFullyQualifiedName: getFullyQualifiedName
93+
getFullyQualifiedName: getFullyQualifiedName,
94+
getEnumMemberValue: getEnumMemberValue
9495
};
9596

9697
var undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined");
@@ -6495,7 +6496,7 @@ module ts {
64956496
}
64966497
}
64976498

6498-
function getConstantValue(node: Expression): number {
6499+
function getConstantValueForExpression(node: Expression): number {
64996500
var isNegative = false;
65006501
if (node.kind === SyntaxKind.PrefixOperator) {
65016502
var unaryExpression = <UnaryExpression>node;
@@ -6512,45 +6513,63 @@ module ts {
65126513
return undefined;
65136514
}
65146515

6516+
function computeEnumMemberValues(node: EnumDeclaration, reportErrors: boolean) {
6517+
var nodeLinks = getNodeLinks(node);
6518+
6519+
if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputedAndChecked)) {
6520+
var enumSymbol = getSymbolOfNode(node);
6521+
var enumType = getDeclaredTypeOfSymbol(enumSymbol);
6522+
var autoValue = 0;
6523+
var ambient = isInAmbientContext(node);
6524+
6525+
forEach(node.members, member => {
6526+
var initializer = member.initializer;
6527+
if (initializer) {
6528+
autoValue = getConstantValueForExpression(initializer);
6529+
if (autoValue === undefined && !ambient) {
6530+
// Only here do we need to check that the initializer is assignable to the enum type.
6531+
// If it is a constant value (not undefined), it is syntactically constrained to be a number.
6532+
// Also, we do not need to check this for ambients because there is already
6533+
// a syntax error if it is not a constant.
6534+
if (reportErrors) {
6535+
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined);
6536+
}
6537+
}
6538+
}
6539+
else if (ambient) {
6540+
autoValue = undefined;
6541+
}
6542+
6543+
if (autoValue !== undefined) {
6544+
getNodeLinks(member).enumMemberValue = autoValue++;
6545+
}
6546+
});
6547+
6548+
if (reportErrors) {
6549+
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputedAndChecked;
6550+
}
6551+
}
6552+
}
6553+
65156554
function checkEnumDeclaration(node: EnumDeclaration) {
65166555
if (!fullTypeCheck) {
65176556
return;
65186557
}
6558+
65196559
checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0);
65206560
checkCollisionWithCapturedThisVariable(node, node.name);
65216561
checkCollistionWithRequireExportsInGeneratedCode(node, node.name);
65226562
checkExportsOnMergedDeclarations(node);
6523-
var enumSymbol = getSymbolOfNode(node);
6524-
var enumType = getDeclaredTypeOfSymbol(enumSymbol);
6525-
var autoValue = 0;
6526-
var ambient = isInAmbientContext(node);
6527-
forEach(node.members, member => {
6528-
var initializer = member.initializer;
6529-
if (initializer) {
6530-
autoValue = getConstantValue(initializer);
6531-
if (autoValue === undefined && !ambient) {
6532-
// Only here do we need to check that the initializer is assignable to the enum type.
6533-
// If it is a constant value (not undefined), it is syntactically constrained to be a number.
6534-
// Also, we do not need to check this for ambients because there is already
6535-
// a syntax error if it is not a constant.
6536-
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined);
6537-
}
6538-
}
6539-
else if (ambient) {
6540-
autoValue = undefined;
6541-
}
65426563

6543-
if (autoValue !== undefined) {
6544-
getNodeLinks(member).enumMemberValue = autoValue++;
6545-
}
6546-
});
6564+
computeEnumMemberValues(node, /*reportErrors:*/ true);
65476565

65486566
// Spec 2014 - Section 9.3:
65496567
// It isn't possible for one enum declaration to continue the automatic numbering sequence of another,
65506568
// and when an enum type has multiple declarations, only one declaration is permitted to omit a value
65516569
// for the first member.
65526570
//
65536571
// Only perform this check once per symbol
6572+
var enumSymbol = getSymbolOfNode(node);
65546573
var firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind);
65556574
if (node === firstDeclaration) {
65566575
var seenEnumMissingInitialInitializer = false;
@@ -7450,17 +7469,6 @@ module ts {
74507469
}
74517470
}
74527471

7453-
function getPropertyAccessSubstitution(node: PropertyAccess): string {
7454-
var symbol = getNodeLinks(node).resolvedSymbol;
7455-
if (symbol && (symbol.flags & SymbolFlags.EnumMember)) {
7456-
var declaration = symbol.valueDeclaration;
7457-
var constantValue: number;
7458-
if (declaration.kind === SyntaxKind.EnumMember && (constantValue = getNodeLinks(declaration).enumMemberValue) !== undefined) {
7459-
return constantValue.toString() + " /* " + identifierToString(declaration.name) + " */";
7460-
}
7461-
}
7462-
}
7463-
74647472
function getExportAssignmentName(node: SourceFile): string {
74657473
var symbol = getExportAssignmentSymbol(getSymbolOfNode(node));
74667474
return symbol && symbolIsValue(symbol) ? symbolToString(symbol): undefined;
@@ -7523,9 +7531,23 @@ module ts {
75237531
}
75247532

75257533
function getEnumMemberValue(node: EnumMember): number {
7534+
computeEnumMemberValues(<EnumDeclaration>node.parent, /*reportErrors:*/ false);
75267535
return getNodeLinks(node).enumMemberValue;
75277536
}
75287537

7538+
function getConstantValue(node: PropertyAccess): number {
7539+
var symbol = getNodeLinks(node).resolvedSymbol;
7540+
if (symbol && (symbol.flags & SymbolFlags.EnumMember)) {
7541+
var declaration = symbol.valueDeclaration;
7542+
var constantValue: number;
7543+
if (declaration.kind === SyntaxKind.EnumMember && (constantValue = getNodeLinks(declaration).enumMemberValue) !== undefined) {
7544+
return constantValue;
7545+
}
7546+
}
7547+
7548+
return undefined;
7549+
}
7550+
75297551
// Create a single instance that we can wrap the underlying emitter TextWriter with. That
75307552
// way we don't have to allocate a new wrapper every time writeTypeAtLocation and
75317553
// writeReturnTypeOfSignatureDeclaration are called.
@@ -7561,7 +7583,6 @@ module ts {
75617583
getProgram: () => program,
75627584
getLocalNameOfContainer: getLocalNameOfContainer,
75637585
getExpressionNamePrefix: getExpressionNamePrefix,
7564-
getPropertyAccessSubstitution: getPropertyAccessSubstitution,
75657586
getExportAssignmentName: getExportAssignmentName,
75667587
isReferencedImportDeclaration: isReferencedImportDeclaration,
75677588
getNodeCheckFlags: getNodeCheckFlags,
@@ -7573,7 +7594,8 @@ module ts {
75737594
writeTypeAtLocation: writeTypeAtLocation,
75747595
writeReturnTypeOfSignatureDeclaration: writeReturnTypeOfSignatureDeclaration,
75757596
isSymbolAccessible: isSymbolAccessible,
7576-
isImportDeclarationEntityNameReferenceDeclarationVisibile: isImportDeclarationEntityNameReferenceDeclarationVisibile
7597+
isImportDeclarationEntityNameReferenceDeclarationVisibile: isImportDeclarationEntityNameReferenceDeclarationVisibile,
7598+
getConstantValue: getConstantValue,
75777599
};
75787600
checkProgram();
75797601
return emitFiles(resolver, targetSourceFile);

src/compiler/emitter.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -868,14 +868,15 @@ module ts {
868868
}
869869

870870
function emitPropertyAccess(node: PropertyAccess) {
871-
var text = resolver.getPropertyAccessSubstitution(node);
872-
if (text) {
873-
write(text);
874-
return;
871+
var constantValue = resolver.getConstantValue(node);
872+
if (constantValue !== undefined) {
873+
write(constantValue.toString() + " /* " + identifierToString(node.right) + " */");
874+
}
875+
else {
876+
emit(node.left);
877+
write(".");
878+
emit(node.right);
875879
}
876-
emit(node.left);
877-
write(".");
878-
emit(node.right);
879880
}
880881

881882
function emitIndexedAccess(node: IndexedAccess) {

src/compiler/types.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,10 @@ module ts {
645645
getAugmentedPropertiesOfApparentType(type: Type): Symbol[];
646646
getRootSymbol(symbol: Symbol): Symbol;
647647
getContextualType(node: Node): Type;
648+
649+
// Returns the constant value of this enum member, or 'undefined' if the enum member has a
650+
// computed value.
651+
getEnumMemberValue(node: EnumMember): number;
648652
}
649653

650654
export interface TextWriter {
@@ -680,7 +684,6 @@ module ts {
680684
getProgram(): Program;
681685
getLocalNameOfContainer(container: Declaration): string;
682686
getExpressionNamePrefix(node: Identifier): string;
683-
getPropertyAccessSubstitution(node: PropertyAccess): string;
684687
getExportAssignmentName(node: SourceFile): string;
685688
isReferencedImportDeclaration(node: ImportDeclaration): boolean;
686689
isTopLevelValueImportedViaEntityName(node: ImportDeclaration): boolean;
@@ -693,6 +696,10 @@ module ts {
693696
writeReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: TextWriter): void;
694697
isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags): SymbolAccessiblityResult;
695698
isImportDeclarationEntityNameReferenceDeclarationVisibile(entityName: EntityName): SymbolAccessiblityResult;
699+
700+
// Returns the constant value this property access resolves to, or 'undefined' if it does
701+
// resolve to a constant.
702+
getConstantValue(node: PropertyAccess): number;
696703
}
697704

698705
export enum SymbolFlags {
@@ -794,13 +801,16 @@ module ts {
794801
}
795802

796803
export enum NodeCheckFlags {
797-
TypeChecked = 0x00000001, // Node has been type checked
798-
LexicalThis = 0x00000002, // Lexical 'this' reference
799-
CaptureThis = 0x00000004, // Lexical 'this' used in body
800-
EmitExtends = 0x00000008, // Emit __extends
801-
SuperInstance = 0x00000010, // Instance 'super' reference
802-
SuperStatic = 0x00000020, // Static 'super' reference
803-
ContextChecked = 0x00000040, // Contextual types have been assigned
804+
TypeChecked = 0x00000001, // Node has been type checked
805+
LexicalThis = 0x00000002, // Lexical 'this' reference
806+
CaptureThis = 0x00000004, // Lexical 'this' used in body
807+
EmitExtends = 0x00000008, // Emit __extends
808+
SuperInstance = 0x00000010, // Instance 'super' reference
809+
SuperStatic = 0x00000020, // Static 'super' reference
810+
ContextChecked = 0x00000040, // Contextual types have been assigned
811+
812+
// Values for enum members have been computed, and any errors have been reported for them.
813+
EnumValuesComputedAndChecked = 0x00000080,
804814
}
805815

806816
export interface NodeLinks {

src/services/services.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2304,12 +2304,14 @@ module ts {
23042304
totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, getContainerNode(node)));
23052305

23062306
if (symbol.flags & SymbolFlags.Property ||
2307-
symbol.flags & SymbolFlags.EnumMember ||
23082307
symbol.flags & SymbolFlags.Variable) {
23092308

23102309
totalParts.push({ text: ":", kind: SymbolDisplayPartKind.punctuation, symbol: undefined });
23112310
totalParts.push({ text: " ", kind: SymbolDisplayPartKind.space, symbol: undefined });
23122311
}
2312+
else if (symbol.flags & SymbolFlags.EnumMember) {
2313+
2314+
}
23132315

23142316
if (addType) {
23152317
var type = typeInfoResolver.getTypeOfSymbol(symbol);

0 commit comments

Comments
 (0)