diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4ff284d9c94..bea464faf2b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2127,7 +2127,7 @@ namespace ts { else if (type.flags & TypeFlags.Reference) { writeTypeReference(type, nextFlags); } - else if (type.flags & (TypeFlags.Class | TypeFlags.Interface | TypeFlags.Enum | TypeFlags.TypeParameter)) { + else if (type.flags & (TypeFlags.Class | TypeFlags.Interface | TypeFlags.EnumLike | TypeFlags.TypeParameter)) { // The specified symbol flags need to be reinterpreted as type flags buildSymbolDisplay(type.symbol, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags); } @@ -3743,23 +3743,72 @@ namespace ts { return links.declaredType; } - function createEnumType(symbol: Symbol): Type { - const type = createType(TypeFlags.Enum); - type.symbol = symbol; - return type; + function isLiteralEnumMember(symbol: Symbol, member: EnumMember) { + const expr = member.initializer; + if (!expr) { + return !isInAmbientContext(member); + } + return expr.kind === SyntaxKind.NumericLiteral || + expr.kind === SyntaxKind.PrefixUnaryExpression && (expr).operator === SyntaxKind.MinusToken && + (expr).operand.kind === SyntaxKind.NumericLiteral || + expr.kind === SyntaxKind.Identifier && hasProperty(symbol.exports, (expr).text); } - function getEnumMemberType(symbol: Symbol): Type { - const links = getSymbolLinks(getParentOfSymbol(symbol)); - const map = links.enumMemberTypes || (links.enumMemberTypes = {}); - const value = "" + getEnumMemberValue(symbol.valueDeclaration); - return map[value] || (map[value] = createEnumType(symbol)); + function enumHasLiteralMembers(symbol: Symbol) { + for (const declaration of symbol.declarations) { + if (declaration.kind === SyntaxKind.EnumDeclaration) { + for (const member of (declaration).members) { + if (!isLiteralEnumMember(symbol, member)) { + return false; + } + } + } + } + return true; } function getDeclaredTypeOfEnum(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.declaredType) { - links.declaredType = symbol.flags & SymbolFlags.EnumMember ? getEnumMemberType(symbol) : createEnumType(symbol); + const enumType = links.declaredType = createType(TypeFlags.Enum); + enumType.symbol = symbol; + if (enumHasLiteralMembers(symbol)) { + const memberTypeList: Type[] = []; + const memberTypes: Map = {}; + for (const declaration of enumType.symbol.declarations) { + if (declaration.kind === SyntaxKind.EnumDeclaration) { + computeEnumMemberValues(declaration); + for (const member of (declaration).members) { + const memberSymbol = getSymbolOfNode(member); + const value = getEnumMemberValue(member); + if (!memberTypes[value]) { + const memberType = memberTypes[value] = createType(TypeFlags.EnumLiteral); + memberType.symbol = memberSymbol; + memberType.baseType = enumType; + memberType.text = "" + value; + memberTypeList.push(memberType); + } + } + } + } + enumType.memberTypes = memberTypes; + if (memberTypeList.length > 1) { + enumType.flags |= TypeFlags.Union; + (enumType).types = memberTypeList; + unionTypes[getTypeListId(memberTypeList)] = enumType; + } + } + } + return links.declaredType; + } + + function getDeclaredTypeOfEnumMember(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.declaredType) { + const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)); + links.declaredType = enumType.flags & TypeFlags.Union ? + enumType.memberTypes[getEnumMemberValue(symbol.valueDeclaration)] : + enumType; } return links.declaredType; } @@ -3793,12 +3842,15 @@ namespace ts { if (symbol.flags & SymbolFlags.TypeAlias) { return getDeclaredTypeOfTypeAlias(symbol); } - if (symbol.flags & (SymbolFlags.Enum | SymbolFlags.EnumMember)) { - return getDeclaredTypeOfEnum(symbol); - } if (symbol.flags & SymbolFlags.TypeParameter) { return getDeclaredTypeOfTypeParameter(symbol); } + if (symbol.flags & SymbolFlags.Enum) { + return getDeclaredTypeOfEnum(symbol); + } + if (symbol.flags & SymbolFlags.EnumMember) { + return getDeclaredTypeOfEnumMember(symbol); + } if (symbol.flags & SymbolFlags.Alias) { return getDeclaredTypeOfAlias(symbol); } @@ -5998,8 +6050,8 @@ namespace ts { } } - function isEnumTypeRelatedTo(source: Type, target: Type, errorReporter?: ErrorReporter) { - if (source.symbol.flags & SymbolFlags.EnumMember && source.symbol.parent === target.symbol) { + function isEnumTypeRelatedTo(source: EnumType, target: EnumType, errorReporter?: ErrorReporter) { + if (source === target) { return true; } if (source.symbol.name !== target.symbol.name || !(source.symbol.flags & SymbolFlags.RegularEnum) || !(target.symbol.flags & SymbolFlags.RegularEnum)) { @@ -6027,12 +6079,14 @@ namespace ts { if (source.flags & TypeFlags.StringLike && target.flags & TypeFlags.String) return true; if (source.flags & TypeFlags.NumberLike && target.flags & TypeFlags.Number) return true; if (source.flags & TypeFlags.BooleanLike && target.flags & TypeFlags.Boolean) return true; - if (source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum && isEnumTypeRelatedTo(source, target, errorReporter)) return true; + if (source.flags & TypeFlags.EnumLiteral && target.flags & TypeFlags.Enum && (source).baseType === target) return true; + if (source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum && isEnumTypeRelatedTo(source, target, errorReporter)) return true; if (source.flags & TypeFlags.Undefined && (!strictNullChecks || target.flags & (TypeFlags.Undefined | TypeFlags.Void))) return true; if (source.flags & TypeFlags.Null && (!strictNullChecks || target.flags & TypeFlags.Null)) return true; if (relation === assignableRelation || relation === comparableRelation) { if (source.flags & TypeFlags.Any) return true; - if (source.flags & TypeFlags.Number && target.flags & TypeFlags.Enum) return true; + if (source.flags & (TypeFlags.Number | TypeFlags.NumberLiteral) && target.flags & TypeFlags.Enum) return true; + if (source.flags & TypeFlags.NumberLiteral && target.flags & TypeFlags.EnumLiteral && (source).text === (target).text) return true; } return false; } @@ -6070,8 +6124,8 @@ namespace ts { relation: Map, errorNode: Node, headMessage?: DiagnosticMessage, - containingMessageChain?: DiagnosticMessageChain): boolean { - + containingMessageChain?: DiagnosticMessageChain): boolean + { let errorInfo: DiagnosticMessageChain; let sourceStack: ObjectType[]; let targetStack: ObjectType[]; @@ -7018,13 +7072,12 @@ namespace ts { } function isUnitType(type: Type): boolean { - return type.flags & (TypeFlags.Literal | TypeFlags.Undefined | TypeFlags.Null) || - type.flags & TypeFlags.Enum && type.symbol.flags & SymbolFlags.EnumMember ? true : false; + return (type.flags & (TypeFlags.Literal | TypeFlags.Undefined | TypeFlags.Null)) !== 0; } function isUnitUnionType(type: Type): boolean { return type.flags & TypeFlags.Boolean ? true : - type.flags & TypeFlags.Union ? !forEach((type).types, t => !isUnitType(t)) : + type.flags & TypeFlags.Union ? type.flags & TypeFlags.Enum ? true : !forEach((type).types, t => !isUnitType(t)) : isUnitType(type); } @@ -7032,8 +7085,8 @@ namespace ts { return type.flags & TypeFlags.StringLiteral ? stringType : type.flags & TypeFlags.NumberLiteral ? numberType : type.flags & TypeFlags.BooleanLiteral ? booleanType : - type.flags & TypeFlags.Enum && type.symbol.flags & SymbolFlags.EnumMember ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)) : - type.flags & TypeFlags.Union ? getUnionType(map((type).types, getBaseTypeOfUnitType)) : + type.flags & TypeFlags.EnumLiteral ? (type).baseType : + type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Enum) ? getUnionType(map((type).types, getBaseTypeOfUnitType), /*noSubtypeReduction*/ true) : type; } @@ -7330,7 +7383,7 @@ namespace ts { } function inferFromTypes(source: Type, target: Type) { - if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union || + if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union && !(source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum) || source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) { // Source and target are both unions or both intersections. If source and target // are the same type, just relate each constituent type to itself. @@ -7758,13 +7811,11 @@ namespace ts { type === emptyStringType ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts : type === emptyStringType ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts; } - if (flags & TypeFlags.Number || type.flags & TypeFlags.Enum && !(type.symbol.flags & SymbolFlags.EnumMember)) { + if (flags & (TypeFlags.Number | TypeFlags.Enum)) { return strictNullChecks ? TypeFacts.NumberStrictFacts : TypeFacts.NumberFacts; } - if (flags & TypeFlags.NumberLike) { - const isZero = type === zeroType || - type.flags & TypeFlags.Enum && type.symbol.flags & SymbolFlags.EnumMember && - getEnumMemberValue(type.symbol.valueDeclaration) === 0; + if (flags & (TypeFlags.NumberLiteral | TypeFlags.EnumLiteral)) { + const isZero = type === zeroType || type.flags & TypeFlags.EnumLiteral && (type).text === "0"; return strictNullChecks ? isZero ? TypeFacts.ZeroStrictFacts : TypeFacts.NonZeroStrictFacts : isZero ? TypeFacts.ZeroFacts : TypeFacts.NonZeroFacts; @@ -10459,7 +10510,7 @@ namespace ts { } let propType = getTypeOfSymbol(prop); - if (prop.flags & SymbolFlags.EnumMember && getParentOfSymbol(prop).flags & SymbolFlags.ConstEnum && isLiteralContextForType(node, propType)) { + if (prop.flags & SymbolFlags.EnumMember && isLiteralContextForType(node, propType)) { propType = getDeclaredTypeOfSymbol(prop); } @@ -13057,16 +13108,16 @@ namespace ts { return getUnionType([type1, type2]); } - function typeContainsEnumLiteral(type: Type, enumType: Type) { + function typeContainsLiteralFromEnum(type: Type, enumType: EnumType) { if (type.flags & TypeFlags.Union) { for (const t of (type).types) { - if (t.flags & TypeFlags.Enum && t.symbol.flags & SymbolFlags.EnumMember && t.symbol.parent === enumType.symbol) { + if (t.flags & TypeFlags.EnumLiteral && (t).baseType === enumType) { return true; } } } - if (type.flags & TypeFlags.Enum) { - return type.symbol.flags & SymbolFlags.EnumMember && type.symbol.parent === enumType.symbol; + if (type.flags & TypeFlags.EnumLiteral) { + return (type).baseType === enumType; } return false; } @@ -13091,13 +13142,13 @@ namespace ts { return maybeTypeOfKind(contextualType, TypeFlags.StringLiteral); } if (type.flags & TypeFlags.Number) { - return maybeTypeOfKind(contextualType, TypeFlags.NumberLiteral); + return maybeTypeOfKind(contextualType, (TypeFlags.NumberLiteral | TypeFlags.EnumLiteral)); } if (type.flags & TypeFlags.Boolean) { return maybeTypeOfKind(contextualType, TypeFlags.BooleanLiteral) && !isTypeAssignableTo(booleanType, contextualType); } - if (type.flags & TypeFlags.Enum && type.symbol.flags & SymbolFlags.ConstEnum) { - return typeContainsEnumLiteral(contextualType, type); + if (type.flags & TypeFlags.Enum) { + return typeContainsLiteralFromEnum(contextualType, type); } } return false; @@ -13889,6 +13940,9 @@ namespace ts { checkTypeArgumentConstraints(typeParameters, node.typeArguments); } } + if (type.flags & TypeFlags.Enum && !(type).memberTypes && getNodeLinks(node).resolvedSymbol.flags & SymbolFlags.EnumMember) { + error(node, Diagnostics.Enum_type_0_has_members_with_initializers_that_are_not_literals, typeToString(type)); + } } } @@ -18400,9 +18454,6 @@ namespace ts { } } - // The built-in boolean type is 'true | false', also mark 'false | true' as a boolean type - createBooleanType([falseType, trueType]); - // Setup global builtins addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 7996f80bd91..7fb3c81d708 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1755,6 +1755,10 @@ "category": "Error", "code": 2534 }, + "Enum type '{0}' has members with initializers that are not literals.": { + "category": "Error", + "code": 2535 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index bd1ba19a054..2dabee35f11 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2068,7 +2068,7 @@ namespace ts { Variable = FunctionScopedVariable | BlockScopedVariable, Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor, Type = Class | Interface | Enum | EnumMember | TypeLiteral | ObjectLiteral | TypeParameter | TypeAlias, - Namespace = ValueModule | NamespaceModule | ConstEnum, + Namespace = ValueModule | NamespaceModule | Enum, Module = ValueModule | NamespaceModule, Accessor = GetAccessor | SetAccessor, @@ -2151,7 +2151,6 @@ 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) - enumMemberTypes?: Map; // Enum member types indexed by enum value } /* @internal */ @@ -2218,50 +2217,52 @@ namespace ts { String = 1 << 1, Number = 1 << 2, Boolean = 1 << 3, - StringLiteral = 1 << 4, // String literal type - NumberLiteral = 1 << 5, - BooleanLiteral = 1 << 6, - ESSymbol = 1 << 7, // Type of symbol primitive introduced in ES6 - Void = 1 << 8, - Undefined = 1 << 9, - Null = 1 << 10, - Never = 1 << 11, // Never type - Enum = 1 << 12, // Enum type - TypeParameter = 1 << 13, // Type parameter - Class = 1 << 14, // Class - Interface = 1 << 15, // Interface - Reference = 1 << 16, // Generic type reference - Tuple = 1 << 17, // Tuple - Union = 1 << 18, // Union (T | U) - Intersection = 1 << 19, // Intersection (T & U) - Anonymous = 1 << 20, // Anonymous - Instantiated = 1 << 21, // Instantiated anonymous type + Enum = 1 << 4, + StringLiteral = 1 << 5, + NumberLiteral = 1 << 6, + BooleanLiteral = 1 << 7, + EnumLiteral = 1 << 8, + ESSymbol = 1 << 9, // Type of symbol primitive introduced in ES6 + Void = 1 << 10, + Undefined = 1 << 11, + Null = 1 << 12, + Never = 1 << 13, // Never type + TypeParameter = 1 << 14, // Type parameter + Class = 1 << 15, // Class + Interface = 1 << 16, // Interface + Reference = 1 << 17, // Generic type reference + Tuple = 1 << 18, // Tuple + Union = 1 << 19, // Union (T | U) + Intersection = 1 << 20, // Intersection (T & U) + Anonymous = 1 << 21, // Anonymous + Instantiated = 1 << 22, // Instantiated anonymous type /* @internal */ - ObjectLiteral = 1 << 22, // Originates in an object literal + ObjectLiteral = 1 << 23, // Originates in an object literal /* @internal */ - FreshObjectLiteral = 1 << 23, // Fresh object literal type + FreshObjectLiteral = 1 << 24, // Fresh object literal type /* @internal */ - ContainsWideningType = 1 << 24, // Type is or contains undefined or null widening type + ContainsWideningType = 1 << 25, // Type is or contains undefined or null widening type /* @internal */ - ContainsObjectLiteral = 1 << 25, // Type is or contains object literal type + ContainsObjectLiteral = 1 << 26, // Type is or contains object literal type /* @internal */ - ContainsAnyFunctionType = 1 << 26, // Type is or contains object literal type - ThisType = 1 << 27, // This type - ObjectLiteralPatternWithComputedProperties = 1 << 28, // Object literal type implied by binding pattern has computed properties + ContainsAnyFunctionType = 1 << 27, // Type is or contains object literal type + ThisType = 1 << 28, // This type + ObjectLiteralPatternWithComputedProperties = 1 << 29, // Object literal type implied by binding pattern has computed properties /* @internal */ Nullable = Undefined | Null, - Literal = StringLiteral | NumberLiteral | BooleanLiteral, + Literal = StringLiteral | NumberLiteral | BooleanLiteral | EnumLiteral, /* @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, /* @internal */ - Primitive = String | Number | Boolean | ESSymbol | Void | Undefined | Null | Literal | Enum, + Primitive = String | Number | Boolean | Enum | ESSymbol | Void | Undefined | Null | Literal, StringLike = String | StringLiteral, - NumberLike = Number | NumberLiteral | Enum, + NumberLike = Number | NumberLiteral | Enum | EnumLiteral, BooleanLike = Boolean | BooleanLiteral, + EnumLike = Enum | EnumLiteral, ObjectType = Class | Interface | Reference | Tuple | Anonymous, UnionOrIntersection = Union | Intersection, StructuredType = ObjectType | Union | Intersection, @@ -2300,6 +2301,16 @@ namespace ts { text: string; // Text of string literal } + // Enum types (TypeFlags.Enum) + export interface EnumType extends Type { + memberTypes: Map; + } + + // Enum types (TypeFlags.EnumLiteral) + export interface EnumLiteralType extends LiteralType { + baseType: EnumType; + } + // Object types (TypeFlags.ObjectType) export interface ObjectType extends Type { } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 6f3a1d6d813..b66abf3cf38 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1523,7 +1523,7 @@ namespace ts { continue; } return parent.kind === SyntaxKind.BinaryExpression && - (parent).operatorToken.kind === SyntaxKind.EqualsToken && + isAssignmentOperator((parent).operatorToken.kind) && (parent).left === node || (parent.kind === SyntaxKind.ForInStatement || parent.kind === SyntaxKind.ForOfStatement) && (parent).initializer === node; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 1ed0b269921..7f41cdbb3a4 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -920,7 +920,7 @@ namespace ts { if (host && host.getScriptKind) { scriptKind = host.getScriptKind(fileName); } - if (!scriptKind || scriptKind === ScriptKind.Unknown) { + if (!scriptKind) { scriptKind = getScriptKindFromFileName(fileName); } return ensureScriptKind(fileName, scriptKind);