Skip to content

[Perf test] Spread enum perf #41055

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

Closed
wants to merge 21 commits into from
Closed
150 changes: 134 additions & 16 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ namespace ts {
getAliasedSymbol: resolveAlias,
getEmitResolver,
getExportsOfModule: getExportsOfModuleAsArray,
getExportsOfSymbol: getExportsOfSymbolAsArray,
getExportsAndPropertiesOfModule,
getSymbolWalker: createGetSymbolWalker(
getRestTypeOfSignature,
Expand Down Expand Up @@ -1261,6 +1262,8 @@ namespace ts {
}
}

function combineSymbolTables(first: SymbolTable, second: SymbolTable): SymbolTable;
function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined;
function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined {
if (!first?.size) return second;
if (!second?.size) return first;
Expand Down Expand Up @@ -1337,20 +1340,25 @@ namespace ts {
}

function addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) {
addToSymbolTableWithDiagnostic(target, source, ({ targetSymbol, id }) => {
forEach(targetSymbol.declarations, addDeclarationDiagnostic(unescapeLeadingUnderscores(id), message));
});

function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) {
return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id));
}
}

function addToSymbolTableWithDiagnostic(target: SymbolTable, source: SymbolTable, onConflict: (args: { targetSymbol: Symbol, sourceSymbol: Symbol, id: __String }) => void) {
source.forEach((sourceSymbol, id) => {
const targetSymbol = target.get(id);
if (targetSymbol) {
// Error on redeclarations
forEach(targetSymbol.declarations, addDeclarationDiagnostic(unescapeLeadingUnderscores(id), message));
onConflict({ targetSymbol, sourceSymbol, id });
}
else {
target.set(id, sourceSymbol);
}
});

function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) {
return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id));
}
}

function getSymbolLinks(symbol: Symbol): SymbolLinks {
Expand Down Expand Up @@ -3424,6 +3432,10 @@ namespace ts {
return symbolsToArray(getExportsOfModule(moduleSymbol));
}

function getExportsOfSymbolAsArray(symbol: Symbol): Symbol[] {
return symbolsToArray(getExportsOfSymbol(symbol));
}

function getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[] {
const exports = getExportsOfModuleAsArray(moduleSymbol);
const exportEquals = resolveExternalModuleSymbol(moduleSymbol);
Expand Down Expand Up @@ -9432,7 +9444,7 @@ namespace ts {
for (const declaration of symbol.declarations) {
if (declaration.kind === SyntaxKind.EnumDeclaration) {
for (const member of (<EnumDeclaration>declaration).members) {
if (member.initializer && isStringLiteralLike(member.initializer)) {
if (isSpreadEnumMember(member) || member.initializer && isStringLiteralLike(member.initializer)) {
return links.enumKind = EnumKind.Literal;
}
if (!isLiteralEnumMember(member)) {
Expand All @@ -9458,14 +9470,16 @@ namespace ts {
const memberTypeList: Type[] = [];
for (const declaration of symbol.declarations) {
if (declaration.kind === SyntaxKind.EnumDeclaration) {
for (const member of (<EnumDeclaration>declaration).members) {
const members = getEnumMembers(<EnumDeclaration>declaration);
members.forEach(member => {
const value = getEnumMemberValue(member);
const memberType = getFreshTypeOfLiteralType(getLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member)));
getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType;
memberTypeList.push(getRegularTypeOfLiteralType(memberType));
}
});
}
}

if (memberTypeList.length) {
const enumType = getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined);
if (enumType.flags & TypeFlags.Union) {
Expand Down Expand Up @@ -9808,6 +9822,28 @@ namespace ts {
return links.resolvedSymbol;
}

function lateBindSpreadEnumMember(earlySymbols: SymbolTable | undefined, lateSymbols: SymbolTable, member: SpreadEnumMember) {
const spreadEnumMemberExports = createSymbolTable();
const enumMembers = getExportsFromSpreadEnumMember(member);
enumMembers.forEach(enumMember => {
const symbol = getSymbolOfNode(enumMember);
const earlySymbol = earlySymbols?.get(symbol.escapedName);
if (earlySymbol) {
const earlySymbolText = symbolToString(earlySymbol);
const diag = error(member, Diagnostics.Spread_enum_member_has_overlapped_on_0, earlySymbolText);
addRelatedInfo(diag, createDiagnosticForNode(earlySymbol.valueDeclaration, Diagnostics._0_is_declared_here, earlySymbolText));
}

const enumSymbol = cloneSymbol(symbol);
spreadEnumMemberExports.set(enumSymbol.escapedName, enumSymbol);
});
addToSymbolTableWithDiagnostic(lateSymbols, spreadEnumMemberExports, ({ targetSymbol }) => {
const targetSymbolText = symbolToString(targetSymbol);
const diag = error(member, Diagnostics.Spread_enum_member_has_overlapped_on_0, targetSymbolText);
addRelatedInfo(diag, createDiagnosticForNode(targetSymbol.valueDeclaration, Diagnostics._0_is_declared_here, targetSymbolText));
});
}

function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): UnderscoreEscapedMap<Symbol> {
const links = getSymbolLinks(symbol);
if (!links[resolutionKind]) {
Expand All @@ -9827,7 +9863,10 @@ namespace ts {
const members = getMembersOfDeclaration(decl);
if (members) {
for (const member of members) {
if (isStatic === hasStaticModifier(member) && hasLateBindableName(member)) {
if (isSpreadEnumMember(member)) {
lateBindSpreadEnumMember(earlySymbols, lateSymbols, member);
}
else if (isStatic === hasStaticModifier(member) && hasLateBindableName(member)) {
lateBindMember(symbol, earlySymbols, lateSymbols, member);
}
}
Expand Down Expand Up @@ -30970,10 +31009,11 @@ namespace ts {
(node.parent.kind === SyntaxKind.ElementAccessExpression && (<ElementAccessExpression>node.parent).expression === node) ||
((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(<Identifier>node) ||
(node.parent.kind === SyntaxKind.TypeQuery && (<TypeQueryNode>node.parent).exprName === node)) ||
(node.parent.kind === SyntaxKind.SpreadEnumMember) ||
(node.parent.kind === SyntaxKind.ExportSpecifier); // We allow reexporting const enums

if (!ok) {
error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query);
error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query_or_spread_enum_member);
}

if (compilerOptions.isolatedModules) {
Expand Down Expand Up @@ -32298,6 +32338,7 @@ namespace ts {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.EnumMember:
case SyntaxKind.SpreadEnumMember:
return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue;
case SyntaxKind.SourceFile:
return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue | DeclarationSpaces.ExportNamespace;
Expand Down Expand Up @@ -35674,9 +35715,11 @@ namespace ts {
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
let autoValue: number | undefined = 0;
for (const member of node.members) {
const value = computeMemberValue(member, autoValue);
getNodeLinks(member).enumMemberValue = value;
autoValue = typeof value === "number" ? value + 1 : undefined;
if (isEnumMember(member)) {
const value = computeMemberValue(member, autoValue);
getNodeLinks(member).enumMemberValue = value;
autoValue = typeof value === "number" ? value + 1 : undefined;
}
}
}
}
Expand Down Expand Up @@ -35852,7 +35895,7 @@ namespace ts {
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name);
checkExportsOnMergedDeclarations(node);
node.members.forEach(checkEnumMember);
node.members.forEach(checkEnumMemberLike);

computeEnumMemberValues(node);

Expand Down Expand Up @@ -35888,7 +35931,7 @@ namespace ts {
}

const firstEnumMember = enumDeclaration.members[0];
if (!firstEnumMember.initializer) {
if (!isSpreadEnumMember(firstEnumMember) && !firstEnumMember.initializer) {
if (seenEnumMissingInitialInitializer) {
error(firstEnumMember.name, Diagnostics.In_an_enum_with_multiple_declarations_only_one_declaration_can_omit_an_initializer_for_its_first_enum_element);
}
Expand All @@ -35900,12 +35943,53 @@ namespace ts {
}
}

function checkEnumMemberLike(node: EnumMemberLike) {
if (isEnumMember(node)) {
checkEnumMember(node);
}
else {
checkSpreadEnumMember(node);
}
}

function checkEnumMember(node: EnumMember) {
if (isPrivateIdentifier(node.name)) {
error(node, Diagnostics.An_enum_member_cannot_be_named_with_a_private_identifier);
}
}

function checkGrammarSpreadEnumMember(symbol: Symbol, container: Symbol, node: SpreadEnumMember) {
if (getEnumKind(symbol) !== EnumKind.Literal) {
error(node.name, Diagnostics.Spread_enum_member_can_only_reference_to_literal_enum_reference);
}

const symbolIsConstEnum = isConstEnumSymbol(symbol);
const containerIsConstEnum = isConstEnumSymbol(container);
if (!compilerOptions.preserveConstEnums && symbolIsConstEnum !== containerIsConstEnum) {
const diag = symbolIsConstEnum ?
Diagnostics.Spread_enum_member_cannot_reference_to_const_enum_without_preserveConstEnums_flag :
Diagnostics.Spread_enum_member_cannot_reference_to_non_const_enum_inside_const_enum_declaration_without_preserveConstEnums_flag;
error(node.name, diag);
}
}

function checkSpreadEnumMember(node: SpreadEnumMember) {
const container = node.parent;
const symbol = getSymbolOfNode(container);
const enumSymbol = getSymbolAtLocation(node.name);
if (!enumSymbol || !(enumSymbol.flags & SymbolFlags.Enum)) {
error(node.name, Diagnostics.Enum_expected);
return;
}
if (symbol === enumSymbol) {
error(node.name, Diagnostics.Spread_enum_member_cannot_reference_to_itself);
}

getExportsOfSymbol(symbol);
checkGrammarSpreadEnumMember(enumSymbol, symbol, node);
checkExpression(node.name);
}

function getFirstNonAmbientClassOrFunctionDeclaration(symbol: Symbol): Declaration | undefined {
const declarations = symbol.declarations;
for (const declaration of declarations) {
Expand Down Expand Up @@ -37967,6 +38051,40 @@ namespace ts {
return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray;
}

function getEnumMembers(node: EnumDeclaration): EnumMember[] {
const declaration = getParseTreeNode(node, isEnumDeclaration);
if (!declaration) {
return emptyArray;
}

const members: EnumMember[] = [];
for (const member of declaration.members) {
if (isEnumMember(member)) {
members.push(member);
}
else {
members.push(...getExportsFromSpreadEnumMember(member));
}
}
return members;
}

function getExportsFromSpreadEnumMember(member: SpreadEnumMember): EnumMember[] {
const enumSymbol = resolveEntityName(member.name, SymbolFlags.Enum);
if (!enumSymbol) {
return emptyArray;
}

const members: EnumMember[] = [];
const enumExports = getExportsOfSymbol(enumSymbol);
enumExports.forEach(enumExport => {
if (enumExport.valueDeclaration && isEnumMember(enumExport.valueDeclaration)) {
members.push(enumExport.valueDeclaration);
}
});
return members;
}

function getNodeCheckFlags(node: Node): NodeCheckFlags {
return getNodeLinks(node).flags || 0;
}
Expand Down
31 changes: 30 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,35 @@
"category": "Error",
"code": 1391
},
"Spread enum member cannot reference to const enum without 'preserveConstEnums' flag.": {
"category": "Error",
"code": 1392
},
"Spread enum member can only reference to literal enum reference.": {
"category": "Error",
"code": 1393
},
"Enum expected.": {
"category": "Error",
"code": 1394
},
"Duplicated enum member referenced by spread enum member.": {
"category": "Error",
"code": 1395
},
"Spread enum member cannot reference to non-const enum inside const enum declaration without 'preserveConstEnums' flag.": {
"category": "Error",
"code": 1396
},
"Spread enum member has overlapped on '{0}'.": {
"category": "Error",
"code": 1397
},
"Spread enum member cannot reference to itself.": {
"category": "Error",
"code": 1398
},

"The types of '{0}' are incompatible between these types.": {
"category": "Error",
"code": 2200
Expand Down Expand Up @@ -1913,7 +1942,7 @@
"category": "Error",
"code": 2474
},
"'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query.": {
"'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query or spread enum member.": {
"category": "Error",
"code": 2475
},
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1534,6 +1534,8 @@ namespace ts {
// Enum
case SyntaxKind.EnumMember:
return emitEnumMember(<EnumMember>node);
case SyntaxKind.SpreadEnumMember:
return emitSpreadEnumMember(<SpreadEnumMember>node);

// JSDoc nodes (only used in codefixes currently)
case SyntaxKind.JSDocParameterTag:
Expand Down Expand Up @@ -3501,6 +3503,11 @@ namespace ts {
emitInitializer(node.initializer, node.name.end, node);
}

function emitSpreadEnumMember(node: SpreadEnumMember) {
emit(node.dotDotDotToken);
emitEntityName(node.name);
}

//
// JSDoc
//
Expand Down
26 changes: 24 additions & 2 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,8 @@ namespace ts {
updateSpreadAssignment,
createEnumMember,
updateEnumMember,
createSpreadEnumMember,
updateSpreadEnumMember,
createSourceFile,
updateSourceFile,
createBundle,
Expand Down Expand Up @@ -3624,7 +3626,7 @@ namespace ts {
decorators: readonly Decorator[] | undefined,
modifiers: readonly Modifier[] | undefined,
name: string | Identifier,
members: readonly EnumMember[]
members: readonly EnumMemberLike[]
) {
const node = createBaseNamedDeclaration<EnumDeclaration>(
SyntaxKind.EnumDeclaration,
Expand All @@ -3646,7 +3648,7 @@ namespace ts {
decorators: readonly Decorator[] | undefined,
modifiers: readonly Modifier[] | undefined,
name: Identifier,
members: readonly EnumMember[]) {
members: readonly EnumMemberLike[]) {
return node.decorators !== decorators
|| node.modifiers !== modifiers
|| node.name !== name
Expand Down Expand Up @@ -4848,6 +4850,26 @@ namespace ts {
: node;
}

// @api
function createSpreadEnumMember(dotDotDotToken: DotDotDotToken, name: EntityName) {
const node = createBaseNode<SpreadEnumMember>(SyntaxKind.SpreadEnumMember);
node.dotDotDotToken = dotDotDotToken;
node.name = name;
node.transformFlags |=
propagateChildFlags(node.dotDotDotToken) |
propagateChildFlags(node.name) |
TransformFlags.ContainsTypeScript;
return node;
}

// @api
function updateSpreadEnumMember(node: SpreadEnumMember, dotDotDotToken: DotDotDotToken, name: EntityName) {
return node.dotDotDotToken !== dotDotDotToken
|| node.name !== name
? update(createSpreadEnumMember(dotDotDotToken, name), node)
: node;
}

//
// Top-level nodes
//
Expand Down
Loading