Skip to content

Commit afee4fb

Browse files
authored
Merge pull request #15486 from Microsoft/literalEnumTypes
String valued members in enums
2 parents 86661b5 + 6a7b6d3 commit afee4fb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+3784
-615
lines changed

src/compiler/checker.ts

Lines changed: 309 additions & 330 deletions
Large diffs are not rendered by default.

src/compiler/declarationEmitter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ namespace ts {
984984
const enumMemberValue = resolver.getConstantValue(node);
985985
if (enumMemberValue !== undefined) {
986986
write(" = ");
987-
write(enumMemberValue.toString());
987+
write(getTextOfConstantValue(enumMemberValue));
988988
}
989989
write(",");
990990
writeLine();

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,6 +1855,10 @@
18551855
"category": "Error",
18561856
"code": 2552
18571857
},
1858+
"Computed values are not permitted in an enum with string valued members.": {
1859+
"category": "Error",
1860+
"code": 2553
1861+
},
18581862
"JSX element attributes type '{0}' may not be a union type.": {
18591863
"category": "Error",
18601864
"code": 2600

src/compiler/emitter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@ namespace ts {
11541154
// check if constant enum value is integer
11551155
const constantValue = getConstantValue(expression);
11561156
// isFinite handles cases when constantValue is undefined
1157-
return isFinite(constantValue)
1157+
return typeof constantValue === "number" && isFinite(constantValue)
11581158
&& Math.floor(constantValue) === constantValue
11591159
&& printerOptions.removeComments;
11601160
}

src/compiler/factory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2371,7 +2371,7 @@ namespace ts {
23712371
/**
23722372
* Sets the constant value to emit for an expression.
23732373
*/
2374-
export function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: number) {
2374+
export function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number) {
23752375
const emitNode = getOrCreateEmitNode(node);
23762376
emitNode.constantValue = value;
23772377
return node;

src/compiler/transformers/ts.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2494,22 +2494,27 @@ namespace ts {
24942494
// we pass false as 'generateNameForComputedPropertyName' for a backward compatibility purposes
24952495
// old emitter always generate 'expression' part of the name as-is.
24962496
const name = getExpressionForPropertyName(member, /*generateNameForComputedPropertyName*/ false);
2497+
const valueExpression = transformEnumMemberDeclarationValue(member);
2498+
const innerAssignment = createAssignment(
2499+
createElementAccess(
2500+
currentNamespaceContainerName,
2501+
name
2502+
),
2503+
valueExpression
2504+
);
2505+
const outerAssignment = valueExpression.kind === SyntaxKind.StringLiteral ?
2506+
innerAssignment :
2507+
createAssignment(
2508+
createElementAccess(
2509+
currentNamespaceContainerName,
2510+
innerAssignment
2511+
),
2512+
name
2513+
);
24972514
return setTextRange(
24982515
createStatement(
24992516
setTextRange(
2500-
createAssignment(
2501-
createElementAccess(
2502-
currentNamespaceContainerName,
2503-
createAssignment(
2504-
createElementAccess(
2505-
currentNamespaceContainerName,
2506-
name
2507-
),
2508-
transformEnumMemberDeclarationValue(member)
2509-
)
2510-
),
2511-
name
2512-
),
2517+
outerAssignment,
25132518
member
25142519
)
25152520
),
@@ -3349,7 +3354,7 @@ namespace ts {
33493354
return node;
33503355
}
33513356

3352-
function tryGetConstEnumValue(node: Node): number {
3357+
function tryGetConstEnumValue(node: Node): string | number {
33533358
if (compilerOptions.isolatedModules) {
33543359
return undefined;
33553360
}

src/compiler/types.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2551,7 +2551,7 @@ namespace ts {
25512551
isUnknownSymbol(symbol: Symbol): boolean;
25522552
/* @internal */ getMergedSymbol(symbol: Symbol): Symbol;
25532553

2554-
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number | undefined;
2554+
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined;
25552555
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean;
25562556
/** Follow all aliases to get the original symbol. */
25572557
getAliasedSymbol(symbol: Symbol): Symbol;
@@ -2776,7 +2776,7 @@ namespace ts {
27762776
isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags, shouldComputeAliasToMarkVisible: boolean): SymbolAccessibilityResult;
27772777
isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult;
27782778
// Returns the constant value this property access resolves to, or 'undefined' for a non-constant
2779-
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number;
2779+
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number;
27802780
getReferencedValueDeclaration(reference: Identifier): Declaration;
27812781
getTypeReferenceSerializationKind(typeName: EntityName, location?: Node): TypeReferenceSerializationKind;
27822782
isOptionalParameter(node: ParameterDeclaration): boolean;
@@ -2914,6 +2914,13 @@ namespace ts {
29142914
isDeclarationWithCollidingName?: boolean; // True if symbol is block scoped redeclaration
29152915
bindingElement?: BindingElement; // Binding element associated with property symbol
29162916
exportsSomeValue?: boolean; // True if module exports some value (not just types)
2917+
enumKind?: EnumKind; // Enum declaration classification
2918+
}
2919+
2920+
/* @internal */
2921+
export const enum EnumKind {
2922+
Numeric, // Numeric enum (each member has a TypeFlags.Enum type)
2923+
Literal // Literal enum (each member has a TypeFlags.EnumLiteral type)
29172924
}
29182925

29192926
/* @internal */
@@ -2986,7 +2993,7 @@ namespace ts {
29862993
resolvedSymbol?: Symbol; // Cached name resolution result
29872994
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
29882995
maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate
2989-
enumMemberValue?: number; // Constant value of enum member
2996+
enumMemberValue?: string | number; // Constant value of enum member
29902997
isVisible?: boolean; // Is this node visible
29912998
containsArgumentsReference?: boolean; // Whether a function-like declaration contains an 'arguments' reference
29922999
hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context
@@ -3006,7 +3013,7 @@ namespace ts {
30063013
StringLiteral = 1 << 5,
30073014
NumberLiteral = 1 << 6,
30083015
BooleanLiteral = 1 << 7,
3009-
EnumLiteral = 1 << 8,
3016+
EnumLiteral = 1 << 8, // Always combined with StringLiteral, NumberLiteral, or Union
30103017
ESSymbol = 1 << 9, // Type of symbol primitive introduced in ES6
30113018
Void = 1 << 10,
30123019
Undefined = 1 << 11,
@@ -3032,17 +3039,17 @@ namespace ts {
30323039

30333040
/* @internal */
30343041
Nullable = Undefined | Null,
3035-
Literal = StringLiteral | NumberLiteral | BooleanLiteral | EnumLiteral,
3042+
Literal = StringLiteral | NumberLiteral | BooleanLiteral,
30363043
StringOrNumberLiteral = StringLiteral | NumberLiteral,
30373044
/* @internal */
30383045
DefinitelyFalsy = StringLiteral | NumberLiteral | BooleanLiteral | Void | Undefined | Null,
30393046
PossiblyFalsy = DefinitelyFalsy | String | Number | Boolean,
30403047
/* @internal */
30413048
Intrinsic = Any | String | Number | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never | NonPrimitive,
30423049
/* @internal */
3043-
Primitive = String | Number | Boolean | Enum | ESSymbol | Void | Undefined | Null | Literal,
3050+
Primitive = String | Number | Boolean | Enum | EnumLiteral | ESSymbol | Void | Undefined | Null | Literal,
30443051
StringLike = String | StringLiteral | Index,
3045-
NumberLike = Number | NumberLiteral | Enum | EnumLiteral,
3052+
NumberLike = Number | NumberLiteral | Enum,
30463053
BooleanLike = Boolean | BooleanLiteral,
30473054
EnumLike = Enum | EnumLiteral,
30483055
UnionOrIntersection = Union | Intersection,
@@ -3081,19 +3088,21 @@ namespace ts {
30813088
// String literal types (TypeFlags.StringLiteral)
30823089
// Numeric literal types (TypeFlags.NumberLiteral)
30833090
export interface LiteralType extends Type {
3084-
text: string; // Text of literal
3091+
value: string | number; // Value of literal
30853092
freshType?: LiteralType; // Fresh version of type
30863093
regularType?: LiteralType; // Regular version of type
30873094
}
30883095

3089-
// Enum types (TypeFlags.Enum)
3090-
export interface EnumType extends Type {
3091-
memberTypes: EnumLiteralType[];
3096+
export interface StringLiteralType extends LiteralType {
3097+
value: string;
30923098
}
30933099

3094-
// Enum types (TypeFlags.EnumLiteral)
3095-
export interface EnumLiteralType extends LiteralType {
3096-
baseType: EnumType & UnionType; // Base enum type
3100+
export interface NumberLiteralType extends LiteralType {
3101+
value: number;
3102+
}
3103+
3104+
// Enum types (TypeFlags.Enum)
3105+
export interface EnumType extends Type {
30973106
}
30983107

30993108
export const enum ObjectFlags {
@@ -3961,7 +3970,7 @@ namespace ts {
39613970
commentRange?: TextRange; // The text range to use when emitting leading or trailing comments
39623971
sourceMapRange?: TextRange; // The text range to use when emitting leading or trailing source mappings
39633972
tokenSourceMapRanges?: TextRange[]; // The text range to use when emitting source mappings for tokens
3964-
constantValue?: number; // The constant value of an expression
3973+
constantValue?: string | number; // The constant value of an expression
39653974
externalHelpersModuleName?: Identifier; // The local name for an imported helpers module
39663975
helpers?: EmitHelper[]; // Emit helpers for the node
39673976
}

src/compiler/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,10 @@ namespace ts {
350350
Debug.fail(`Literal kind '${node.kind}' not accounted for.`);
351351
}
352352

353+
export function getTextOfConstantValue(value: string | number) {
354+
return typeof value === "string" ? '"' + escapeNonAsciiString(value) + '"' : "" + value;
355+
}
356+
353357
// Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__'
354358
export function escapeIdentifier(identifier: string): string {
355359
return identifier.length >= 2 && identifier.charCodeAt(0) === CharacterCodes._ && identifier.charCodeAt(1) === CharacterCodes._ ? "_" + identifier : identifier;

src/services/completions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ namespace ts.Completions {
275275
}
276276
}
277277
else if (type.flags & TypeFlags.StringLiteral) {
278-
const name = (<LiteralType>type).text;
278+
const name = (<StringLiteralType>type).value;
279279
if (!uniques.has(name)) {
280280
uniques.set(name, true);
281281
result.push({

src/services/symbolDisplay.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,8 @@ namespace ts.SymbolDisplay {
336336
displayParts.push(spacePart());
337337
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
338338
displayParts.push(spacePart());
339-
displayParts.push(displayPart(constantValue.toString(), SymbolDisplayPartKind.numericLiteral));
339+
displayParts.push(displayPart(getTextOfConstantValue(constantValue),
340+
typeof constantValue === "number" ? SymbolDisplayPartKind.numericLiteral : SymbolDisplayPartKind.stringLiteral));
340341
}
341342
}
342343
}

0 commit comments

Comments
 (0)