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

Widening and non-widening computed enum types #52542

Merged
merged 6 commits into from
Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 @@ -9654,8 +9655,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 @@ -11898,8 +11899,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 @@ -11913,9 +11914,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 @@ -11925,7 +11926,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 @@ -11935,6 +11936,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 @@ -16176,7 +16187,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 @@ -18026,25 +18037,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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps this "family" of functions could be renamed now? they all (isFreshLiteralType, getRegularTypeOfLiteralType, getFreshTypeOfLiteralType, getRegularTypeOfLiteralType) mention "literal" type but now they will also handle enums and enums are not literal types

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but isFreshFreshableType and getFreshTypeOfFreshableType are pretty awful. 😮

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, can't argue that 😅 OTOH, those names would be more correct - they sound a little bit weird at first but if we focus on their meaning, it makes sense.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fresh type of bel-air!

I suppose it could be argued that Enum.A is an “enum literal”, even if it’s not a literal in the grammatical sense. The compiler is treating it as one in a way by giving it its own nominal type (Enum)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main problem from my PoV, is that "literal" is already used in the codebase and it often doesn't include enums:

Literal = StringLiteral | NumberLiteral | BigIntLiteral | BooleanLiteral,

I see now though that EnumLiteral also exist so maybe it isn't a big deal.

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 @@ -22935,7 +22946,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 @@ -22950,7 +22961,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 @@ -25587,7 +25598,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 @@ -45896,7 +45907,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 @@ -47657,7 +47668,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 @@ -6028,7 +6028,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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Freshable = Enum | Literal,
/** @internal */
Freshable = Enum | Literal,

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intended for this to be public.

StringOrNumberLiteral = StringLiteral | NumberLiteral,
/** @internal */
StringOrNumberLiteralOrUnique = StringLiteral | NumberLiteral | UniqueESSymbol,
Expand Down Expand Up @@ -6122,22 +6123,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
Comment on lines -6125 to +6128
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this intended to be public API now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

}

/** @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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could technically still keep FreshableType if you left the members in place.

export interface LiteralType extends FreshableType {}
export interface LiteralType {
    freshType: LiteralType;
    regularType: LiteralType;

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 @@ -6159,7 +6158,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 @@ -6659,7 +6659,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 @@ -6711,10 +6712,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 @@ -6729,7 +6732,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 @@ -2684,7 +2684,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 @@ -2736,10 +2737,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 @@ -2754,7 +2757,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