Provide capability to ask the checker what the constant value of an enum member is.

This commit is contained in:
Cyrus Najmabadi 2014-09-21 13:58:15 -07:00
parent 89eff5608d
commit fae7a3560e
4 changed files with 89 additions and 54 deletions

View File

@ -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 = <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(<EnumDeclaration>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);

View File

@ -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) {

View File

@ -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 {

View File

@ -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);