Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

String valued members in enums #15486

Merged
merged 22 commits into from
May 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
639 changes: 309 additions & 330 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/compiler/declarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ namespace ts {
const enumMemberValue = resolver.getConstantValue(node);
if (enumMemberValue !== undefined) {
write(" = ");
write(enumMemberValue.toString());
write(getTextOfConstantValue(enumMemberValue));
}
write(",");
writeLine();
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1855,6 +1855,10 @@
"category": "Error",
"code": 2552
},
"Computed values are not permitted in an enum with string valued members.": {
"category": "Error",
"code": 2553
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1154,7 +1154,7 @@ namespace ts {
// check if constant enum value is integer
const constantValue = getConstantValue(expression);
// isFinite handles cases when constantValue is undefined
return isFinite(constantValue)
return typeof constantValue === "number" && isFinite(constantValue)
&& Math.floor(constantValue) === constantValue
&& printerOptions.removeComments;
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2371,7 +2371,7 @@ namespace ts {
/**
* Sets the constant value to emit for an expression.
*/
export function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: number) {
export function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number) {
const emitNode = getOrCreateEmitNode(node);
emitNode.constantValue = value;
return node;
Expand Down
33 changes: 19 additions & 14 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2494,22 +2494,27 @@ namespace ts {
// we pass false as 'generateNameForComputedPropertyName' for a backward compatibility purposes
// old emitter always generate 'expression' part of the name as-is.
const name = getExpressionForPropertyName(member, /*generateNameForComputedPropertyName*/ false);
const valueExpression = transformEnumMemberDeclarationValue(member);
const innerAssignment = createAssignment(
createElementAccess(
currentNamespaceContainerName,
name
),
valueExpression
);
const outerAssignment = valueExpression.kind === SyntaxKind.StringLiteral ?
innerAssignment :
createAssignment(
createElementAccess(
currentNamespaceContainerName,
innerAssignment
),
name
);
return setTextRange(
createStatement(
setTextRange(
createAssignment(
createElementAccess(
currentNamespaceContainerName,
createAssignment(
createElementAccess(
currentNamespaceContainerName,
name
),
transformEnumMemberDeclarationValue(member)
)
),
name
),
outerAssignment,
member
)
),
Expand Down Expand Up @@ -3349,7 +3354,7 @@ namespace ts {
return node;
}

function tryGetConstEnumValue(node: Node): number {
function tryGetConstEnumValue(node: Node): string | number {
if (compilerOptions.isolatedModules) {
return undefined;
}
Expand Down
39 changes: 24 additions & 15 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2551,7 +2551,7 @@ namespace ts {
isUnknownSymbol(symbol: Symbol): boolean;
/* @internal */ getMergedSymbol(symbol: Symbol): Symbol;

getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number | undefined;
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined;
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean;
/** Follow all aliases to get the original symbol. */
getAliasedSymbol(symbol: Symbol): Symbol;
Expand Down Expand Up @@ -2776,7 +2776,7 @@ namespace ts {
isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags, shouldComputeAliasToMarkVisible: boolean): SymbolAccessibilityResult;
isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult;
// Returns the constant value this property access resolves to, or 'undefined' for a non-constant
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number;
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number;
getReferencedValueDeclaration(reference: Identifier): Declaration;
getTypeReferenceSerializationKind(typeName: EntityName, location?: Node): TypeReferenceSerializationKind;
isOptionalParameter(node: ParameterDeclaration): boolean;
Expand Down Expand Up @@ -2914,6 +2914,13 @@ namespace ts {
isDeclarationWithCollidingName?: boolean; // True if symbol is block scoped redeclaration
bindingElement?: BindingElement; // Binding element associated with property symbol
exportsSomeValue?: boolean; // True if module exports some value (not just types)
enumKind?: EnumKind; // Enum declaration classification
}

/* @internal */
export const enum EnumKind {
Numeric, // Numeric enum (each member has a TypeFlags.Enum type)
Literal // Literal enum (each member has a TypeFlags.EnumLiteral type)
}

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

/* @internal */
Nullable = Undefined | Null,
Literal = StringLiteral | NumberLiteral | BooleanLiteral | EnumLiteral,
Literal = StringLiteral | NumberLiteral | BooleanLiteral,
StringOrNumberLiteral = StringLiteral | NumberLiteral,
/* @internal */
DefinitelyFalsy = StringLiteral | NumberLiteral | BooleanLiteral | Void | Undefined | Null,
PossiblyFalsy = DefinitelyFalsy | String | Number | Boolean,
/* @internal */
Intrinsic = Any | String | Number | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never | NonPrimitive,
/* @internal */
Primitive = String | Number | Boolean | Enum | ESSymbol | Void | Undefined | Null | Literal,
Primitive = String | Number | Boolean | Enum | EnumLiteral | ESSymbol | Void | Undefined | Null | Literal,
StringLike = String | StringLiteral | Index,
NumberLike = Number | NumberLiteral | Enum | EnumLiteral,
NumberLike = Number | NumberLiteral | Enum,
BooleanLike = Boolean | BooleanLiteral,
EnumLike = Enum | EnumLiteral,
UnionOrIntersection = Union | Intersection,
Expand Down Expand Up @@ -3081,19 +3088,21 @@ namespace ts {
// String literal types (TypeFlags.StringLiteral)
// Numeric literal types (TypeFlags.NumberLiteral)
export interface LiteralType extends Type {
text: string; // Text of literal
value: string | number; // Value of literal
freshType?: LiteralType; // Fresh version of type
regularType?: LiteralType; // Regular version of type
}

// Enum types (TypeFlags.Enum)
export interface EnumType extends Type {
memberTypes: EnumLiteralType[];
export interface StringLiteralType extends LiteralType {
value: string;
}

// Enum types (TypeFlags.EnumLiteral)
export interface EnumLiteralType extends LiteralType {
baseType: EnumType & UnionType; // Base enum type
export interface NumberLiteralType extends LiteralType {
value: number;
}

// Enum types (TypeFlags.Enum)
export interface EnumType extends Type {
}

export const enum ObjectFlags {
Expand Down Expand Up @@ -3961,7 +3970,7 @@ namespace ts {
commentRange?: TextRange; // The text range to use when emitting leading or trailing comments
sourceMapRange?: TextRange; // The text range to use when emitting leading or trailing source mappings
tokenSourceMapRanges?: TextRange[]; // The text range to use when emitting source mappings for tokens
constantValue?: number; // The constant value of an expression
constantValue?: string | number; // The constant value of an expression
externalHelpersModuleName?: Identifier; // The local name for an imported helpers module
helpers?: EmitHelper[]; // Emit helpers for the node
}
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,10 @@ namespace ts {
Debug.fail(`Literal kind '${node.kind}' not accounted for.`);
}

export function getTextOfConstantValue(value: string | number) {
return typeof value === "string" ? '"' + escapeNonAsciiString(value) + '"' : "" + value;
}

// Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__'
export function escapeIdentifier(identifier: string): string {
return identifier.length >= 2 && identifier.charCodeAt(0) === CharacterCodes._ && identifier.charCodeAt(1) === CharacterCodes._ ? "_" + identifier : identifier;
Expand Down
2 changes: 1 addition & 1 deletion src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ namespace ts.Completions {
}
}
else if (type.flags & TypeFlags.StringLiteral) {
const name = (<LiteralType>type).text;
const name = (<StringLiteralType>type).value;
if (!uniques.has(name)) {
uniques.set(name, true);
result.push({
Expand Down
3 changes: 2 additions & 1 deletion src/services/symbolDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,8 @@ namespace ts.SymbolDisplay {
displayParts.push(spacePart());
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
displayParts.push(spacePart());
displayParts.push(displayPart(constantValue.toString(), SymbolDisplayPartKind.numericLiteral));
displayParts.push(displayPart(getTextOfConstantValue(constantValue),
typeof constantValue === "number" ? SymbolDisplayPartKind.numericLiteral : SymbolDisplayPartKind.stringLiteral));
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/bestCommonTypeOfTuple.types
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ var e3 = t3[2]; // any
>2 : 2

var e4 = t4[3]; // number
>e4 : number | E1 | E2
>t4[3] : number | E1 | E2
>e4 : number
>t4[3] : number
>t4 : [E1, E2, number]
>3 : 3

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var o = {
>{ [E1.x || E2.x]: 0} : { [x: number]: number; }

[E1.x || E2.x]: 0
>E1.x || E2.x : E1 | E2
>E1.x || E2.x : E2
>E1.x : E1
>E1 : typeof E1
>x : E1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var o = {
>{ [E1.x || E2.x]: 0} : { [x: number]: number; }

[E1.x || E2.x]: 0
>E1.x || E2.x : E1 | E2
>E1.x || E2.x : E2
>E1.x : E1
>E1 : typeof E1
>x : E1
Expand Down
20 changes: 14 additions & 6 deletions tests/baselines/reference/enumAssignmentCompat3.errors.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
tests/cases/compiler/enumAssignmentCompat3.ts(68,1): error TS2324: Property 'd' is missing in type 'First.E'.
tests/cases/compiler/enumAssignmentCompat3.ts(68,1): error TS2322: Type 'Abcd.E' is not assignable to type 'First.E'.
Property 'd' is missing in type 'First.E'.
tests/cases/compiler/enumAssignmentCompat3.ts(70,1): error TS2322: Type 'Cd.E' is not assignable to type 'First.E'.
Property 'd' is missing in type 'First.E'.
tests/cases/compiler/enumAssignmentCompat3.ts(71,1): error TS2322: Type 'Nope' is not assignable to type 'E'.
tests/cases/compiler/enumAssignmentCompat3.ts(72,1): error TS2322: Type 'Decl.E' is not assignable to type 'First.E'.
tests/cases/compiler/enumAssignmentCompat3.ts(75,1): error TS2324: Property 'c' is missing in type 'Ab.E'.
tests/cases/compiler/enumAssignmentCompat3.ts(75,1): error TS2322: Type 'First.E' is not assignable to type 'Ab.E'.
Property 'c' is missing in type 'Ab.E'.
tests/cases/compiler/enumAssignmentCompat3.ts(76,1): error TS2322: Type 'First.E' is not assignable to type 'Cd.E'.
Property 'a' is missing in type 'Cd.E'.
tests/cases/compiler/enumAssignmentCompat3.ts(77,1): error TS2322: Type 'E' is not assignable to type 'Nope'.
tests/cases/compiler/enumAssignmentCompat3.ts(78,1): error TS2322: Type 'First.E' is not assignable to type 'Decl.E'.
tests/cases/compiler/enumAssignmentCompat3.ts(82,1): error TS2322: Type 'Const.E' is not assignable to type 'First.E'.
tests/cases/compiler/enumAssignmentCompat3.ts(83,1): error TS2322: Type 'First.E' is not assignable to type 'Const.E'.
tests/cases/compiler/enumAssignmentCompat3.ts(86,1): error TS2324: Property 'd' is missing in type 'First.E'.
tests/cases/compiler/enumAssignmentCompat3.ts(86,1): error TS2322: Type 'Merged.E' is not assignable to type 'First.E'.
Property 'd' is missing in type 'First.E'.


==== tests/cases/compiler/enumAssignmentCompat3.ts (11 errors) ====
Expand Down Expand Up @@ -82,7 +86,8 @@ tests/cases/compiler/enumAssignmentCompat3.ts(86,1): error TS2324: Property 'd'
abc = secondAbc; // ok
abc = secondAbcd; // missing 'd'
~~~
!!! error TS2324: Property 'd' is missing in type 'First.E'.
!!! error TS2322: Type 'Abcd.E' is not assignable to type 'First.E'.
!!! error TS2322: Property 'd' is missing in type 'First.E'.
abc = secondAb; // ok
abc = secondCd; // missing 'd'
~~~
Expand All @@ -98,10 +103,12 @@ tests/cases/compiler/enumAssignmentCompat3.ts(86,1): error TS2324: Property 'd'
secondAbcd = abc; // ok
secondAb = abc; // missing 'c'
~~~~~~~~
!!! error TS2324: Property 'c' is missing in type 'Ab.E'.
!!! error TS2322: Type 'First.E' is not assignable to type 'Ab.E'.
!!! error TS2322: Property 'c' is missing in type 'Ab.E'.
secondCd = abc; // missing 'a' and 'b'
~~~~~~~~
!!! error TS2322: Type 'First.E' is not assignable to type 'Cd.E'.
!!! error TS2322: Property 'a' is missing in type 'Cd.E'.
nope = abc; // nope!
~~~~
!!! error TS2322: Type 'E' is not assignable to type 'Nope'.
Expand All @@ -121,7 +128,8 @@ tests/cases/compiler/enumAssignmentCompat3.ts(86,1): error TS2324: Property 'd'
// merged enums compare all their members
abc = merged; // missing 'd'
~~~
!!! error TS2324: Property 'd' is missing in type 'First.E'.
!!! error TS2322: Type 'Merged.E' is not assignable to type 'First.E'.
!!! error TS2322: Property 'd' is missing in type 'First.E'.
merged = abc; // ok
abc = merged2; // ok
merged2 = abc; // ok
Loading