Skip to content

Commit afd39cc

Browse files
committed
Enum type is also a union of the literal enum types it declares
1 parent 451f48b commit afd39cc

5 files changed

Lines changed: 140 additions & 74 deletions

File tree

src/compiler/checker.ts

Lines changed: 93 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2127,7 +2127,7 @@ namespace ts {
21272127
else if (type.flags & TypeFlags.Reference) {
21282128
writeTypeReference(<TypeReference>type, nextFlags);
21292129
}
2130-
else if (type.flags & (TypeFlags.Class | TypeFlags.Interface | TypeFlags.Enum | TypeFlags.TypeParameter)) {
2130+
else if (type.flags & (TypeFlags.Class | TypeFlags.Interface | TypeFlags.EnumLike | TypeFlags.TypeParameter)) {
21312131
// The specified symbol flags need to be reinterpreted as type flags
21322132
buildSymbolDisplay(type.symbol, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags);
21332133
}
@@ -3743,23 +3743,72 @@ namespace ts {
37433743
return links.declaredType;
37443744
}
37453745

3746-
function createEnumType(symbol: Symbol): Type {
3747-
const type = createType(TypeFlags.Enum);
3748-
type.symbol = symbol;
3749-
return type;
3746+
function isLiteralEnumMember(symbol: Symbol, member: EnumMember) {
3747+
const expr = member.initializer;
3748+
if (!expr) {
3749+
return !isInAmbientContext(member);
3750+
}
3751+
return expr.kind === SyntaxKind.NumericLiteral ||
3752+
expr.kind === SyntaxKind.PrefixUnaryExpression && (<PrefixUnaryExpression>expr).operator === SyntaxKind.MinusToken &&
3753+
(<PrefixUnaryExpression>expr).operand.kind === SyntaxKind.NumericLiteral ||
3754+
expr.kind === SyntaxKind.Identifier && hasProperty(symbol.exports, (<Identifier>expr).text);
37503755
}
37513756

3752-
function getEnumMemberType(symbol: Symbol): Type {
3753-
const links = getSymbolLinks(getParentOfSymbol(symbol));
3754-
const map = links.enumMemberTypes || (links.enumMemberTypes = {});
3755-
const value = "" + getEnumMemberValue(<EnumMember>symbol.valueDeclaration);
3756-
return map[value] || (map[value] = createEnumType(symbol));
3757+
function enumHasLiteralMembers(symbol: Symbol) {
3758+
for (const declaration of symbol.declarations) {
3759+
if (declaration.kind === SyntaxKind.EnumDeclaration) {
3760+
for (const member of (<EnumDeclaration>declaration).members) {
3761+
if (!isLiteralEnumMember(symbol, member)) {
3762+
return false;
3763+
}
3764+
}
3765+
}
3766+
}
3767+
return true;
37573768
}
37583769

37593770
function getDeclaredTypeOfEnum(symbol: Symbol): Type {
37603771
const links = getSymbolLinks(symbol);
37613772
if (!links.declaredType) {
3762-
links.declaredType = symbol.flags & SymbolFlags.EnumMember ? getEnumMemberType(symbol) : createEnumType(symbol);
3773+
const enumType = links.declaredType = <EnumType>createType(TypeFlags.Enum);
3774+
enumType.symbol = symbol;
3775+
if (enumHasLiteralMembers(symbol)) {
3776+
const memberTypeList: Type[] = [];
3777+
const memberTypes: Map<EnumLiteralType> = {};
3778+
for (const declaration of enumType.symbol.declarations) {
3779+
if (declaration.kind === SyntaxKind.EnumDeclaration) {
3780+
computeEnumMemberValues(<EnumDeclaration>declaration);
3781+
for (const member of (<EnumDeclaration>declaration).members) {
3782+
const memberSymbol = getSymbolOfNode(member);
3783+
const value = getEnumMemberValue(member);
3784+
if (!memberTypes[value]) {
3785+
const memberType = memberTypes[value] = <EnumLiteralType>createType(TypeFlags.EnumLiteral);
3786+
memberType.symbol = memberSymbol;
3787+
memberType.baseType = enumType;
3788+
memberType.text = "" + value;
3789+
memberTypeList.push(memberType);
3790+
}
3791+
}
3792+
}
3793+
}
3794+
enumType.memberTypes = memberTypes;
3795+
if (memberTypeList.length > 1) {
3796+
enumType.flags |= TypeFlags.Union;
3797+
(<EnumType & UnionType>enumType).types = memberTypeList;
3798+
unionTypes[getTypeListId(memberTypeList)] = <EnumType & UnionType>enumType;
3799+
}
3800+
}
3801+
}
3802+
return links.declaredType;
3803+
}
3804+
3805+
function getDeclaredTypeOfEnumMember(symbol: Symbol): Type {
3806+
const links = getSymbolLinks(symbol);
3807+
if (!links.declaredType) {
3808+
const enumType = <EnumType>getDeclaredTypeOfEnum(getParentOfSymbol(symbol));
3809+
links.declaredType = enumType.flags & TypeFlags.Union ?
3810+
enumType.memberTypes[getEnumMemberValue(<EnumDeclaration>symbol.valueDeclaration)] :
3811+
enumType;
37633812
}
37643813
return links.declaredType;
37653814
}
@@ -3793,12 +3842,15 @@ namespace ts {
37933842
if (symbol.flags & SymbolFlags.TypeAlias) {
37943843
return getDeclaredTypeOfTypeAlias(symbol);
37953844
}
3796-
if (symbol.flags & (SymbolFlags.Enum | SymbolFlags.EnumMember)) {
3797-
return getDeclaredTypeOfEnum(symbol);
3798-
}
37993845
if (symbol.flags & SymbolFlags.TypeParameter) {
38003846
return getDeclaredTypeOfTypeParameter(symbol);
38013847
}
3848+
if (symbol.flags & SymbolFlags.Enum) {
3849+
return getDeclaredTypeOfEnum(symbol);
3850+
}
3851+
if (symbol.flags & SymbolFlags.EnumMember) {
3852+
return getDeclaredTypeOfEnumMember(symbol);
3853+
}
38023854
if (symbol.flags & SymbolFlags.Alias) {
38033855
return getDeclaredTypeOfAlias(symbol);
38043856
}
@@ -5998,8 +6050,8 @@ namespace ts {
59986050
}
59996051
}
60006052

6001-
function isEnumTypeRelatedTo(source: Type, target: Type, errorReporter?: ErrorReporter) {
6002-
if (source.symbol.flags & SymbolFlags.EnumMember && source.symbol.parent === target.symbol) {
6053+
function isEnumTypeRelatedTo(source: EnumType, target: EnumType, errorReporter?: ErrorReporter) {
6054+
if (source === target) {
60036055
return true;
60046056
}
60056057
if (source.symbol.name !== target.symbol.name || !(source.symbol.flags & SymbolFlags.RegularEnum) || !(target.symbol.flags & SymbolFlags.RegularEnum)) {
@@ -6027,12 +6079,14 @@ namespace ts {
60276079
if (source.flags & TypeFlags.StringLike && target.flags & TypeFlags.String) return true;
60286080
if (source.flags & TypeFlags.NumberLike && target.flags & TypeFlags.Number) return true;
60296081
if (source.flags & TypeFlags.BooleanLike && target.flags & TypeFlags.Boolean) return true;
6030-
if (source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum && isEnumTypeRelatedTo(source, target, errorReporter)) return true;
6082+
if (source.flags & TypeFlags.EnumLiteral && target.flags & TypeFlags.Enum && (<EnumLiteralType>source).baseType === target) return true;
6083+
if (source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum && isEnumTypeRelatedTo(<EnumType>source, <EnumType>target, errorReporter)) return true;
60316084
if (source.flags & TypeFlags.Undefined && (!strictNullChecks || target.flags & (TypeFlags.Undefined | TypeFlags.Void))) return true;
60326085
if (source.flags & TypeFlags.Null && (!strictNullChecks || target.flags & TypeFlags.Null)) return true;
60336086
if (relation === assignableRelation || relation === comparableRelation) {
60346087
if (source.flags & TypeFlags.Any) return true;
6035-
if (source.flags & TypeFlags.Number && target.flags & TypeFlags.Enum) return true;
6088+
if (source.flags & (TypeFlags.Number | TypeFlags.NumberLiteral) && target.flags & TypeFlags.Enum) return true;
6089+
if (source.flags & TypeFlags.NumberLiteral && target.flags & TypeFlags.EnumLiteral && (<LiteralType>source).text === (<LiteralType>target).text) return true;
60366090
}
60376091
return false;
60386092
}
@@ -6070,8 +6124,8 @@ namespace ts {
60706124
relation: Map<RelationComparisonResult>,
60716125
errorNode: Node,
60726126
headMessage?: DiagnosticMessage,
6073-
containingMessageChain?: DiagnosticMessageChain): boolean {
6074-
6127+
containingMessageChain?: DiagnosticMessageChain): boolean
6128+
{
60756129
let errorInfo: DiagnosticMessageChain;
60766130
let sourceStack: ObjectType[];
60776131
let targetStack: ObjectType[];
@@ -7018,22 +7072,21 @@ namespace ts {
70187072
}
70197073

70207074
function isUnitType(type: Type): boolean {
7021-
return type.flags & (TypeFlags.Literal | TypeFlags.Undefined | TypeFlags.Null) ||
7022-
type.flags & TypeFlags.Enum && type.symbol.flags & SymbolFlags.EnumMember ? true : false;
7075+
return (type.flags & (TypeFlags.Literal | TypeFlags.Undefined | TypeFlags.Null)) !== 0;
70237076
}
70247077

70257078
function isUnitUnionType(type: Type): boolean {
70267079
return type.flags & TypeFlags.Boolean ? true :
7027-
type.flags & TypeFlags.Union ? !forEach((<UnionType>type).types, t => !isUnitType(t)) :
7080+
type.flags & TypeFlags.Union ? type.flags & TypeFlags.Enum ? true : !forEach((<UnionType>type).types, t => !isUnitType(t)) :
70287081
isUnitType(type);
70297082
}
70307083

70317084
function getBaseTypeOfUnitType(type: Type): Type {
70327085
return type.flags & TypeFlags.StringLiteral ? stringType :
70337086
type.flags & TypeFlags.NumberLiteral ? numberType :
70347087
type.flags & TypeFlags.BooleanLiteral ? booleanType :
7035-
type.flags & TypeFlags.Enum && type.symbol.flags & SymbolFlags.EnumMember ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)) :
7036-
type.flags & TypeFlags.Union ? getUnionType(map((<UnionType>type).types, getBaseTypeOfUnitType)) :
7088+
type.flags & TypeFlags.EnumLiteral ? (<EnumLiteralType>type).baseType :
7089+
type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Enum) ? getUnionType(map((<UnionType>type).types, getBaseTypeOfUnitType), /*noSubtypeReduction*/ true) :
70377090
type;
70387091
}
70397092

@@ -7330,7 +7383,7 @@ namespace ts {
73307383
}
73317384

73327385
function inferFromTypes(source: Type, target: Type) {
7333-
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union ||
7386+
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union && !(source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum) ||
73347387
source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) {
73357388
// Source and target are both unions or both intersections. If source and target
73367389
// are the same type, just relate each constituent type to itself.
@@ -7758,13 +7811,11 @@ namespace ts {
77587811
type === emptyStringType ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts :
77597812
type === emptyStringType ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts;
77607813
}
7761-
if (flags & TypeFlags.Number || type.flags & TypeFlags.Enum && !(type.symbol.flags & SymbolFlags.EnumMember)) {
7814+
if (flags & (TypeFlags.Number | TypeFlags.Enum)) {
77627815
return strictNullChecks ? TypeFacts.NumberStrictFacts : TypeFacts.NumberFacts;
77637816
}
7764-
if (flags & TypeFlags.NumberLike) {
7765-
const isZero = type === zeroType ||
7766-
type.flags & TypeFlags.Enum && type.symbol.flags & SymbolFlags.EnumMember &&
7767-
getEnumMemberValue(<EnumMember>type.symbol.valueDeclaration) === 0;
7817+
if (flags & (TypeFlags.NumberLiteral | TypeFlags.EnumLiteral)) {
7818+
const isZero = type === zeroType || type.flags & TypeFlags.EnumLiteral && (<LiteralType>type).text === "0";
77687819
return strictNullChecks ?
77697820
isZero ? TypeFacts.ZeroStrictFacts : TypeFacts.NonZeroStrictFacts :
77707821
isZero ? TypeFacts.ZeroFacts : TypeFacts.NonZeroFacts;
@@ -10459,7 +10510,7 @@ namespace ts {
1045910510
}
1046010511

1046110512
let propType = getTypeOfSymbol(prop);
10462-
if (prop.flags & SymbolFlags.EnumMember && getParentOfSymbol(prop).flags & SymbolFlags.ConstEnum && isLiteralContextForType(<Expression>node, propType)) {
10513+
if (prop.flags & SymbolFlags.EnumMember && isLiteralContextForType(<Expression>node, propType)) {
1046310514
propType = getDeclaredTypeOfSymbol(prop);
1046410515
}
1046510516

@@ -13057,16 +13108,16 @@ namespace ts {
1305713108
return getUnionType([type1, type2]);
1305813109
}
1305913110

13060-
function typeContainsEnumLiteral(type: Type, enumType: Type) {
13111+
function typeContainsLiteralFromEnum(type: Type, enumType: EnumType) {
1306113112
if (type.flags & TypeFlags.Union) {
1306213113
for (const t of (<UnionType>type).types) {
13063-
if (t.flags & TypeFlags.Enum && t.symbol.flags & SymbolFlags.EnumMember && t.symbol.parent === enumType.symbol) {
13114+
if (t.flags & TypeFlags.EnumLiteral && (<EnumLiteralType>t).baseType === enumType) {
1306413115
return true;
1306513116
}
1306613117
}
1306713118
}
13068-
if (type.flags & TypeFlags.Enum) {
13069-
return type.symbol.flags & SymbolFlags.EnumMember && type.symbol.parent === enumType.symbol;
13119+
if (type.flags & TypeFlags.EnumLiteral) {
13120+
return (<EnumLiteralType>type).baseType === enumType;
1307013121
}
1307113122
return false;
1307213123
}
@@ -13091,13 +13142,13 @@ namespace ts {
1309113142
return maybeTypeOfKind(contextualType, TypeFlags.StringLiteral);
1309213143
}
1309313144
if (type.flags & TypeFlags.Number) {
13094-
return maybeTypeOfKind(contextualType, TypeFlags.NumberLiteral);
13145+
return maybeTypeOfKind(contextualType, (TypeFlags.NumberLiteral | TypeFlags.EnumLiteral));
1309513146
}
1309613147
if (type.flags & TypeFlags.Boolean) {
1309713148
return maybeTypeOfKind(contextualType, TypeFlags.BooleanLiteral) && !isTypeAssignableTo(booleanType, contextualType);
1309813149
}
13099-
if (type.flags & TypeFlags.Enum && type.symbol.flags & SymbolFlags.ConstEnum) {
13100-
return typeContainsEnumLiteral(contextualType, type);
13150+
if (type.flags & TypeFlags.Enum) {
13151+
return typeContainsLiteralFromEnum(contextualType, <EnumType>type);
1310113152
}
1310213153
}
1310313154
return false;
@@ -13889,6 +13940,9 @@ namespace ts {
1388913940
checkTypeArgumentConstraints(typeParameters, node.typeArguments);
1389013941
}
1389113942
}
13943+
if (type.flags & TypeFlags.Enum && !(<EnumType>type).memberTypes && getNodeLinks(node).resolvedSymbol.flags & SymbolFlags.EnumMember) {
13944+
error(node, Diagnostics.Enum_type_0_has_members_with_initializers_that_are_not_literals, typeToString(type));
13945+
}
1389213946
}
1389313947
}
1389413948

@@ -18400,9 +18454,6 @@ namespace ts {
1840018454
}
1840118455
}
1840218456

18403-
// The built-in boolean type is 'true | false', also mark 'false | true' as a boolean type
18404-
createBooleanType([falseType, trueType]);
18405-
1840618457
// Setup global builtins
1840718458
addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0);
1840818459

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1755,6 +1755,10 @@
17551755
"category": "Error",
17561756
"code": 2534
17571757
},
1758+
"Enum type '{0}' has members with initializers that are not literals.": {
1759+
"category": "Error",
1760+
"code": 2535
1761+
},
17581762
"JSX element attributes type '{0}' may not be a union type.": {
17591763
"category": "Error",
17601764
"code": 2600

0 commit comments

Comments
 (0)