Skip to content

Commit

Permalink
Widening and non-widening computed enum types (#52542)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahejlsberg authored Feb 15, 2023
1 parent de2ab4a commit 7d00bb0
Show file tree
Hide file tree
Showing 22 changed files with 980 additions and 60 deletions.
51 changes: 31 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ import {
entityNameToString,
EnumDeclaration,
EnumMember,
EnumType,
equateValues,
escapeLeadingUnderscores,
escapeString,
Expand Down Expand Up @@ -9660,8 +9661,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const t = types[i];
flags |= t.flags;
if (!(t.flags & TypeFlags.Nullable)) {
if (t.flags & (TypeFlags.BooleanLiteral | TypeFlags.EnumLiteral)) {
const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLiteralType(t as LiteralType);
if (t.flags & (TypeFlags.BooleanLiteral | TypeFlags.EnumLike)) {
const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLikeType(t as LiteralType);
if (baseType.flags & TypeFlags.Union) {
const count = (baseType as UnionType).types.length;
if (i + count <= types.length && getRegularTypeOfLiteralType(types[i + count - 1]) === getRegularTypeOfLiteralType((baseType as UnionType).types[count - 1])) {
Expand Down Expand Up @@ -11906,8 +11907,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return links.declaredType;
}

function getBaseTypeOfEnumLiteralType(type: Type) {
return type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union) ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)!) : type;
function getBaseTypeOfEnumLikeType(type: Type) {
return type.flags & TypeFlags.EnumLike && type.symbol.flags & SymbolFlags.EnumMember ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)!) : type;
}

function getDeclaredTypeOfEnum(symbol: Symbol): Type {
Expand All @@ -11921,9 +11922,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (hasBindableName(member)) {
const memberSymbol = getSymbolOfDeclaration(member);
const value = getEnumMemberValue(member);
const memberType = value !== undefined ?
getFreshTypeOfLiteralType(getEnumLiteralType(value, getSymbolId(symbol), memberSymbol)) :
createTypeWithSymbol(TypeFlags.Enum, memberSymbol);
const memberType = getFreshTypeOfLiteralType(value !== undefined ?
getEnumLiteralType(value, getSymbolId(symbol), memberSymbol) :
createComputedEnumType(memberSymbol));
getSymbolLinks(memberSymbol).declaredType = memberType;
memberTypeList.push(getRegularTypeOfLiteralType(memberType));
}
Expand All @@ -11933,7 +11934,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
const enumType = memberTypeList.length ?
getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined) :
createTypeWithSymbol(TypeFlags.Enum, symbol);
createComputedEnumType(symbol);
if (enumType.flags & TypeFlags.Union) {
enumType.flags |= TypeFlags.EnumLiteral;
enumType.symbol = symbol;
Expand All @@ -11943,6 +11944,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return links.declaredType;
}

function createComputedEnumType(symbol: Symbol) {
const regularType = createTypeWithSymbol(TypeFlags.Enum, symbol) as EnumType;
const freshType = createTypeWithSymbol(TypeFlags.Enum, symbol) as EnumType;
regularType.regularType = regularType;
regularType.freshType = freshType;
freshType.regularType = regularType;
freshType.freshType = freshType;
return regularType;
}

function getDeclaredTypeOfEnumMember(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (!links.declaredType) {
Expand Down Expand Up @@ -16201,7 +16212,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
orderedRemoveItemAt(typeSet, 1);
}
}
if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || includes & TypeFlags.Void && includes & TypeFlags.Undefined) {
if (includes & (TypeFlags.Enum | TypeFlags.Literal | TypeFlags.UniqueESSymbol | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || includes & TypeFlags.Void && includes & TypeFlags.Undefined) {
removeRedundantLiteralTypes(typeSet, includes, !!(unionReduction & UnionReduction.Subtype));
}
if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) {
Expand Down Expand Up @@ -18046,25 +18057,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function getFreshTypeOfLiteralType(type: Type): Type {
if (type.flags & TypeFlags.Literal) {
if (!(type as LiteralType).freshType) {
if (type.flags & TypeFlags.Freshable) {
if (!(type as FreshableType).freshType) {
const freshType = createLiteralType(type.flags, (type as LiteralType).value, (type as LiteralType).symbol, type as LiteralType);
freshType.freshType = freshType;
(type as LiteralType).freshType = freshType;
(type as FreshableType).freshType = freshType;
}
return (type as LiteralType).freshType;
return (type as FreshableType).freshType;
}
return type;
}

function getRegularTypeOfLiteralType(type: Type): Type {
return type.flags & TypeFlags.Literal ? (type as LiteralType).regularType :
return type.flags & TypeFlags.Freshable ? (type as FreshableType).regularType :
type.flags & TypeFlags.Union ? ((type as UnionType).regularType || ((type as UnionType).regularType = mapType(type, getRegularTypeOfLiteralType) as UnionType)) :
type;
}

function isFreshLiteralType(type: Type) {
return !!(type.flags & TypeFlags.Literal) && (type as LiteralType).freshType === type;
return !!(type.flags & TypeFlags.Freshable) && (type as LiteralType).freshType === type;
}

function getStringLiteralType(value: string): StringLiteralType {
Expand Down Expand Up @@ -22993,7 +23004,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function getBaseTypeOfLiteralType(type: Type): Type {
return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(type as LiteralType) :
return type.flags & TypeFlags.EnumLike ? getBaseTypeOfEnumLikeType(type as LiteralType) :
type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType :
type.flags & TypeFlags.NumberLiteral ? numberType :
type.flags & TypeFlags.BigIntLiteral ? bigintType :
Expand All @@ -23008,7 +23019,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function getWidenedLiteralType(type: Type): Type {
return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(type as LiteralType) :
return type.flags & TypeFlags.EnumLike && isFreshLiteralType(type) ? getBaseTypeOfEnumLikeType(type as LiteralType) :
type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType :
type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType :
type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType :
Expand Down Expand Up @@ -25652,7 +25663,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
return true;
}
if (source.flags & TypeFlags.EnumLiteral && getBaseTypeOfEnumLiteralType(source as LiteralType) === target) {
if (source.flags & TypeFlags.EnumLike && getBaseTypeOfEnumLikeType(source as LiteralType) === target) {
return true;
}
return containsType(target.types, source);
Expand Down Expand Up @@ -45995,7 +46006,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression {
const enumResult = type.flags & TypeFlags.EnumLiteral ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing, /*flags*/ undefined, tracker)
const enumResult = type.flags & TypeFlags.EnumLike ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing, /*flags*/ undefined, tracker)
: type === trueType ? factory.createTrue() : type === falseType && factory.createFalse();
if (enumResult) return enumResult;
const literalValue = (type as LiteralType).value;
Expand Down Expand Up @@ -47782,7 +47793,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function isSimpleLiteralEnumReference(expr: Expression) {
if ((isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression))) &&
isEntityNameExpression(expr.expression)) {
return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLiteral);
return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLike);
}
}

Expand Down
19 changes: 9 additions & 10 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6043,7 +6043,8 @@ export const enum TypeFlags {
/** @internal */
Nullable = Undefined | Null,
Literal = StringLiteral | NumberLiteral | BigIntLiteral | BooleanLiteral,
Unit = Literal | UniqueESSymbol | Nullable,
Unit = Enum | Literal | UniqueESSymbol | Nullable,
Freshable = Enum | Literal,
StringOrNumberLiteral = StringLiteral | NumberLiteral,
/** @internal */
StringOrNumberLiteralOrUnique = StringLiteral | NumberLiteral | UniqueESSymbol,
Expand Down Expand Up @@ -6137,22 +6138,20 @@ export interface NullableType extends IntrinsicType {
objectFlags: ObjectFlags;
}

/** @internal */
export interface FreshableIntrinsicType extends IntrinsicType {
freshType: IntrinsicType; // Fresh version of type
regularType: IntrinsicType; // Regular version of type
export interface FreshableType extends Type {
freshType: FreshableType; // Fresh version of type
regularType: FreshableType; // Regular version of type
}

/** @internal */
export type FreshableType = LiteralType | FreshableIntrinsicType;
export interface FreshableIntrinsicType extends FreshableType, IntrinsicType {
}

// String literal types (TypeFlags.StringLiteral)
// Numeric literal types (TypeFlags.NumberLiteral)
// BigInt literal types (TypeFlags.BigIntLiteral)
export interface LiteralType extends Type {
export interface LiteralType extends FreshableType {
value: string | number | PseudoBigInt; // Value of literal
freshType: LiteralType; // Fresh version of type
regularType: LiteralType; // Regular version of type
}

// Unique symbol types (TypeFlags.UniqueESSymbol)
Expand All @@ -6174,7 +6173,7 @@ export interface BigIntLiteralType extends LiteralType {
}

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

// Types included in TypeFlags.ObjectFlagsType have an objectFlags property. Some ObjectFlags
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ambientDeclarations.types
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ declare enum E3 {
>E3 : E3

A
>A : E3
>A : E3.A
}
declare module E3 {
>E3 : typeof E3
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ambientEnum1.types
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
>E2 : E2

x = 'foo'.length
>x : E2
>x : E2.x
>'foo'.length : number
>'foo' : "foo"
>length : number
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ambientErrors.types
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ declare enum E2 {
>E2 : E2

x = 'foo'.length
>x : E2
>x : E2.x
>'foo'.length : number
>'foo' : "foo"
>length : number
Expand Down
13 changes: 8 additions & 5 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6675,7 +6675,8 @@ declare namespace ts {
TemplateLiteral = 134217728,
StringMapping = 268435456,
Literal = 2944,
Unit = 109440,
Unit = 109472,
Freshable = 2976,
StringOrNumberLiteral = 384,
PossiblyFalsy = 117724,
StringLike = 402653316,
Expand Down Expand Up @@ -6727,10 +6728,12 @@ declare namespace ts {
isClass(): this is InterfaceType;
isIndexType(): this is IndexType;
}
interface LiteralType extends Type {
interface FreshableType extends Type {
freshType: FreshableType;
regularType: FreshableType;
}
interface LiteralType extends FreshableType {
value: string | number | PseudoBigInt;
freshType: LiteralType;
regularType: LiteralType;
}
interface UniqueESSymbolType extends Type {
symbol: Symbol;
Expand All @@ -6745,7 +6748,7 @@ declare namespace ts {
interface BigIntLiteralType extends LiteralType {
value: PseudoBigInt;
}
interface EnumType extends Type {
interface EnumType extends FreshableType {
}
enum ObjectFlags {
None = 0,
Expand Down
13 changes: 8 additions & 5 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2700,7 +2700,8 @@ declare namespace ts {
TemplateLiteral = 134217728,
StringMapping = 268435456,
Literal = 2944,
Unit = 109440,
Unit = 109472,
Freshable = 2976,
StringOrNumberLiteral = 384,
PossiblyFalsy = 117724,
StringLike = 402653316,
Expand Down Expand Up @@ -2752,10 +2753,12 @@ declare namespace ts {
isClass(): this is InterfaceType;
isIndexType(): this is IndexType;
}
interface LiteralType extends Type {
interface FreshableType extends Type {
freshType: FreshableType;
regularType: FreshableType;
}
interface LiteralType extends FreshableType {
value: string | number | PseudoBigInt;
freshType: LiteralType;
regularType: LiteralType;
}
interface UniqueESSymbolType extends Type {
symbol: Symbol;
Expand All @@ -2770,7 +2773,7 @@ declare namespace ts {
interface BigIntLiteralType extends LiteralType {
value: PseudoBigInt;
}
interface EnumType extends Type {
interface EnumType extends FreshableType {
}
enum ObjectFlags {
None = 0,
Expand Down
Loading

0 comments on commit 7d00bb0

Please sign in to comment.