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

Implement erasable Enum Annotations #61414

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Changes from 9 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
22 changes: 22 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
@@ -57,6 +57,7 @@
ElementAccessExpression,
EntityNameExpression,
EnumDeclaration,
EnumLiteralExpression,
escapeLeadingUnderscores,
every,
ExportAssignment,
@@ -84,6 +85,7 @@
FunctionLikeDeclaration,
GetAccessorDeclaration,
getAssignedExpandoInitializer,
getAssignedName,

Check warning on line 88 in src/compiler/binder.ts

GitHub Actions / lint

'getAssignedName' is defined but never used. Allowed unused vars must match /^(_+$|_[^_])/u
getAssignmentDeclarationKind,
getAssignmentDeclarationPropertyAccessKind,
getCombinedModifierFlags,
@@ -155,6 +157,8 @@
isEmptyObjectLiteral,
isEntityNameExpression,
isEnumConst,
isEnumLiteralExpression,
isEnumTypeReference,

Check warning on line 161 in src/compiler/binder.ts

GitHub Actions / lint

'isEnumTypeReference' is defined but never used. Allowed unused vars must match /^(_+$|_[^_])/u
isExportAssignment,
isExportDeclaration,
isExportsIdentifier,
@@ -822,6 +826,9 @@
if (isNamedDeclaration(node)) {
setParent(node.name, node);
}
if (isEnumLiteralExpression(node) && symbol.valueDeclaration === node.parent) {
// This is not a real redeclaration, but is in fact two separate meanings for the same symbol.
}
// Report errors every position with duplicate declaration
// Report errors on previous encountered declarations
let message = symbol.flags & SymbolFlags.BlockScopedVariable
@@ -2272,6 +2279,7 @@
case SyntaxKind.ClassDeclaration:
return declareClassMember(node, symbolFlags, symbolExcludes);

case SyntaxKind.EnumLiteralExpression:
case SyntaxKind.EnumDeclaration:
return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes);

@@ -3044,6 +3052,8 @@
return bindBlockScopedDeclaration(node as Declaration, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes);
case SyntaxKind.TypeAliasDeclaration:
return bindBlockScopedDeclaration(node as Declaration, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
case SyntaxKind.EnumLiteralExpression:
return bindEnumLiteralExpression(node as EnumLiteralExpression);
case SyntaxKind.EnumDeclaration:
return bindEnumDeclaration(node as EnumDeclaration);
case SyntaxKind.ModuleDeclaration:
@@ -3655,6 +3665,17 @@
: bindBlockScopedDeclaration(node, SymbolFlags.RegularEnum, SymbolFlags.RegularEnumExcludes);
}

function bindEnumLiteralExpression(node: EnumLiteralExpression) {
Debug.assert(isVariableDeclaration(node.parent));

const varSymbol: Symbol = node.parent.symbol;
node.symbol = varSymbol;
varSymbol.flags |= isEnumConst(node) ? SymbolFlags.ConstEnum : SymbolFlags.RegularEnum;
varSymbol.flags &= ~(SymbolFlags.Assignment | SymbolFlags.Variable);
varSymbol.exports ??= createSymbolTable();
appendIfUnique(varSymbol.declarations, node);
}

function bindVariableDeclarationOrBindingElement(node: VariableDeclaration | BindingElement) {
if (inStrictMode) {
checkStrictModeEvalOrArguments(node, node.name);
@@ -3914,6 +3935,7 @@
case SyntaxKind.ClassExpression:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.EnumLiteralExpression:
case SyntaxKind.ObjectLiteralExpression:
case SyntaxKind.TypeLiteral:
case SyntaxKind.JSDocTypeLiteral:
43 changes: 36 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
@@ -169,6 +169,7 @@ import {
EntityNameOrEntityNameExpression,
entityNameToString,
EnumDeclaration,
EnumLiteralExpression,
EnumMember,
EnumType,
equateValues,
@@ -536,7 +537,11 @@ import {
isEntityNameExpression,
isEnumConst,
isEnumDeclaration,
isEnumLiteralDeclaration,
isEnumLiteralExpression,
isEnumMember,
isEnumTypeAnnotation,
isEnumTypeReference,
isExclusivelyTypeOnlyImportOrExport,
isExpandoPropertyDeclaration,
isExportAssignment,
@@ -11984,7 +11989,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
// getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive.
// Re-dispatch based on valueDeclaration.kind instead.
else if (isEnumDeclaration(declaration)) {
else if (isEnumDeclaration(declaration) || isEnumLiteralExpression(declaration)) {
type = getTypeOfFuncClassEnumModule(symbol);
}
else if (isEnumMember(declaration)) {
@@ -12876,7 +12881,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const memberTypeList: Type[] = [];
if (symbol.declarations) {
for (const declaration of symbol.declarations) {
if (declaration.kind === SyntaxKind.EnumDeclaration) {
if (declaration.kind === SyntaxKind.EnumDeclaration || declaration.kind === SyntaxKind.EnumLiteralExpression) {
for (const member of (declaration as EnumDeclaration).members) {
if (hasBindableName(member)) {
const memberSymbol = getSymbolOfDeclaration(member);
@@ -16706,6 +16711,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
links.resolvedSymbol = unknownSymbol;
return links.resolvedType = checkExpressionCached(node.parent.expression);
}
// `var MyEnum: enum = { FirstValue: 1, SecondValue: 2 }` should resolve to a union of the enum values.
if (isEnumTypeReference(node) && isVariableDeclaration(node.parent) && node.parent.initializer && isEnumLiteralExpression(node.parent.initializer)) {
return links.resolvedType = checkExpressionCached(node.parent.initializer);
}
let symbol: Symbol | undefined;
let type: Type | undefined;
const meaning = SymbolFlags.Type;
@@ -16760,6 +16769,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.EnumLiteralExpression:
return declaration;
}
}
@@ -41109,6 +41119,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return checkArrayLiteral(node as ArrayLiteralExpression, checkMode, forceTuple);
case SyntaxKind.ObjectLiteralExpression:
return checkObjectLiteral(node as ObjectLiteralExpression, checkMode);
case SyntaxKind.EnumLiteralExpression:
return checkEnumLiteralExpression(node as EnumLiteralExpression);
case SyntaxKind.PropertyAccessExpression:
return checkPropertyAccessExpression(node as PropertyAccessExpression, checkMode);
case SyntaxKind.QualifiedName:
@@ -44128,7 +44140,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
checkClassNameCollisionWithObject(name);
}
}
else if (isEnumDeclaration(node)) {
else if (isEnumDeclaration(node) || (isVariableDeclaration(node) && node.initializer && isEnumLiteralExpression(node.initializer))) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

here too

checkTypeNameIsReserved(name, Diagnostics.Enum_name_cannot_be_0);
}
}
@@ -47033,16 +47045,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}

function computeEnumMemberValues(node: EnumDeclaration) {
function computeEnumMemberValues(node: EnumDeclaration | EnumLiteralExpression) {
const nodeLinks = getNodeLinks(node);
if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) {
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
let autoValue: number | undefined = 0;
// EnumLiteralExpressions are essentially plain ObjectLiteralExpressions and can not have computed values.
const hasComputedValues = !isEnumLiteralExpression(node);
let autoValue: number | undefined = hasComputedValues ? 0 : undefined;
let previous: EnumMember | undefined;
for (const member of node.members) {
const result = computeEnumMemberValue(member, autoValue, previous);
getNodeLinks(member).enumMemberValue = result;
autoValue = typeof result.value === "number" ? result.value + 1 : undefined;
autoValue = (hasComputedValues && typeof result.value === "number") ? result.value + 1 : undefined;
previous = member;
}
}
@@ -47191,6 +47205,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
addLazyDiagnostic(() => checkEnumDeclarationWorker(node));
}

function checkEnumLiteralExpression(node: EnumLiteralExpression) {
addLazyDiagnostic(() => checkEnumDeclarationWorker(node as any));
return getTypeOfSymbol(getSymbolOfDeclaration(node));
}

function checkEnumDeclarationWorker(node: EnumDeclaration) {
// Grammar checking
checkGrammarModifiers(node);
@@ -47218,7 +47237,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const enumIsConst = isEnumConst(node);
// check that const is placed\omitted on all enum declarations
forEach(enumSymbol.declarations, decl => {
if (isEnumDeclaration(decl) && isEnumConst(decl) !== enumIsConst) {
if ((isEnumDeclaration(decl) || isEnumLiteralExpression(decl)) && isEnumConst(decl) !== enumIsConst) {
error(getNameOfDeclaration(decl), Diagnostics.Enum_declarations_must_all_be_const_or_non_const);
}
});
@@ -48854,6 +48873,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.ModuleDeclaration:
copyLocallyVisibleExportSymbols(getSymbolOfDeclaration(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember);
break;
case SyntaxKind.EnumLiteralExpression:
case SyntaxKind.EnumDeclaration:
copySymbols(getSymbolOfDeclaration(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember);
break;
@@ -49304,6 +49324,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// no other meta properties are valid syntax, thus no others should have symbols
return undefined;
}
else if (isEnumTypeAnnotation(node)) {
// Avoid symbolizing "enum" keywords in type annotations.
return undefined;
}
}

switch (node.kind) {
@@ -49490,6 +49514,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (isDeclarationNameOrImportPropertyName(node)) {
const symbol = getSymbolAtLocation(node);
if (symbol) {
if (symbol.valueDeclaration && isEnumLiteralDeclaration(symbol.valueDeclaration)) {
return getDeclaredTypeOfEnum(symbol);
}
return getTypeOfSymbol(symbol);
}
return errorType;
@@ -50352,6 +50379,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.PropertyAssignment:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.EnumMember:
case SyntaxKind.EnumLiteralExpression:
case SyntaxKind.ObjectLiteralExpression:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
@@ -51329,6 +51357,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
findFirstModifierExcept(node, SyntaxKind.AwaitKeyword) :
find(node.modifiers, isModifier);
case SyntaxKind.EnumDeclaration:
case SyntaxKind.EnumLiteralExpression:
return findFirstModifierExcept(node, SyntaxKind.ConstKeyword);
default:
Debug.assertNever(node);
10 changes: 10 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
@@ -79,6 +79,7 @@ import {
ensureTrailingDirectorySeparator,
EntityName,
EnumDeclaration,
EnumLiteralExpression,
EnumMember,
escapeJsxAttributeString,
escapeLeadingUnderscores,
@@ -1924,6 +1925,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
return emitArrayLiteralExpression(node as ArrayLiteralExpression);
case SyntaxKind.ObjectLiteralExpression:
return emitObjectLiteralExpression(node as ObjectLiteralExpression);
case SyntaxKind.EnumLiteralExpression:
return emitEnumLiteralExpression(node as EnumLiteralExpression);
case SyntaxKind.PropertyAccessExpression:
return emitPropertyAccessExpression(node as PropertyAccessExpression);
case SyntaxKind.ElementAccessExpression:
@@ -2617,6 +2620,13 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
popNameGenerationScope(node);
}

function emitEnumLiteralExpression(node: EnumLiteralExpression) {
writeSpace();
writePunctuation("{");
emitList(node, node.members, ListFormat.EnumMembers);
writePunctuation("}");
}

function emitPropertyAccessExpression(node: PropertyAccessExpression) {
emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess);
const token = node.questionDotToken || setTextRangePosEnd(factory.createToken(SyntaxKind.DotToken) as DotToken, node.expression.end, node.name.pos);
39 changes: 39 additions & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
@@ -77,6 +77,7 @@ import {
EndOfFileToken,
EntityName,
EnumDeclaration,
EnumLiteralExpression,
EnumMember,
EqualsGreaterThanToken,
escapeLeadingUnderscores,
@@ -160,6 +161,7 @@ import {
isElementAccessChain,
isElementAccessExpression,
isEnumDeclaration,
isEnumLiteralExpression,
isExclamationToken,
isExportAssignment,
isExportDeclaration,
@@ -754,6 +756,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
updateTypeAliasDeclaration,
createEnumDeclaration,
updateEnumDeclaration,
createEnumLiteralExpression,
updateEnumLiteralExpression,
createModuleDeclaration,
updateModuleDeclaration,
createModuleBlock,
@@ -4539,6 +4543,40 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
: node;
}

// @api
function createEnumLiteralExpression(
modifiers: readonly ModifierLike[] | undefined,
name: string | Identifier,
members: readonly EnumMember[],
) {
const node = createBaseDeclaration<EnumLiteralExpression>(SyntaxKind.EnumLiteralExpression);
node.modifiers = asNodeArray(modifiers);
node.name = asName(name);
node.members = createNodeArray(members);
node.transformFlags |= propagateChildrenFlags(node.modifiers) |
propagateChildFlags(node.name) |
propagateChildrenFlags(node.members) |
TransformFlags.ContainsTypeScript;
node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Enum declarations cannot contain `await`

node.jsDoc = undefined; // initialized by parser (JsDocContainer)
return node;
}

// @api
function updateEnumLiteralExpression(
node: EnumLiteralExpression,
modifiers: readonly ModifierLike[] | undefined,
name: Identifier,
members: readonly EnumMember[],
) {
return node.modifiers !== modifiers
|| node.name !== name
|| node.members !== members
? update(createEnumLiteralExpression(modifiers, name, members), node)
: node;
}

// @api
function createModuleDeclaration(
modifiers: readonly ModifierLike[] | undefined,
@@ -7086,6 +7124,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
isInterfaceDeclaration(node) ? updateInterfaceDeclaration(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) :
isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, modifierArray, node.name, node.typeParameters, node.type) :
isEnumDeclaration(node) ? updateEnumDeclaration(node, modifierArray, node.name, node.members) :
isEnumLiteralExpression(node) ? updateEnumLiteralExpression(node, modifierArray, node.name, node.members) :
isModuleDeclaration(node) ? updateModuleDeclaration(node, modifierArray, node.name, node.body) :
isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, modifierArray, node.isTypeOnly, node.name, node.moduleReference) :
isImportDeclaration(node) ? updateImportDeclaration(node, modifierArray, node.importClause, node.moduleSpecifier, node.attributes) :
5 changes: 5 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
@@ -47,6 +47,7 @@ import {
ElementAccessExpression,
EmptyStatement,
EnumDeclaration,
EnumLiteralExpression,
EnumMember,
EqualsGreaterThanToken,
ExclamationToken,
@@ -817,6 +818,10 @@ export function isEnumDeclaration(node: Node): node is EnumDeclaration {
return node.kind === SyntaxKind.EnumDeclaration;
}

export function isEnumLiteralExpression(node: Node): node is EnumLiteralExpression {
return node.kind === SyntaxKind.EnumLiteralExpression;
}

export function isModuleDeclaration(node: Node): node is ModuleDeclaration {
return node.kind === SyntaxKind.ModuleDeclaration;
}
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.