TypeScript
TypeScript copied to clipboard
When there is only one member in an enumDeclaration, const enum E {A = 1}, the declared type of the enumDeclaration is incorrect
🔎 Search Terms
We use the version of TypeScript is v4.9.5.
🕗 Version & Regression Information
- This changed between versions v4.9.5 and v5.8.x.
- This changed in commit or PR _______
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about _________
- I was unable to test this on prior versions because the case of
const enum EnumD1 { A1 = 1 }
⏯ Playground Link
No response
💻 Code
// Your code here
const enum EnumD1 {
A1 = 1
}
const enum EnumD2 {
A1 = 1, B1 = 2
}
export {EnumD1}
export {EnumD2}
🙁 Actual behavior
// the source code about TypeScript
function getDeclaredTypeOfEnum(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (links.declaredType) {
return links.declaredType;
}
if (getEnumKind(symbol) === EnumKind.Literal) {
enumCount++;
const memberTypeList: Type[] = [];
if (symbol.declarations) {
for (const declaration of symbol.declarations) {
if (declaration.kind === SyntaxKind.EnumDeclaration) {
for (const member of (declaration as EnumDeclaration).members) {
const value = getEnumMemberValue(member);
const memberType = getFreshTypeOfLiteralType(getEnumLiteralType(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**) {
enumType.flags |= TypeFlags.EnumLiteral;
**enumType.symbol = symbol;**
}
**return links.declaredType = enumType;**
}
}
const enumType = createType(TypeFlags.Enum);
enumType.symbol = symbol;
return links.declaredType = enumType;
}
When const enum EnumD1 { A1 = 1 } is used, EnumD1 is not a Union type. Therefore, the links.declaredType obtained is A1 instead of EnumD1, which is not expected. When const enum EnumD2 { A1 = 1, B1 = 2 } is used, EnumD1 is a Union type. Therefore, the links.declaredType obtained is EnumD2, which is expected.
🙂 Expected behavior
When const enum EnumD1 { A1 = 1 } is used, the links.declaredType obtained is EnumD1.
Additional information about the issue
No response
What's the observable manifestation of this difference? SymbolLinks is internal.
We defined an isConstEnum function to determine whether the obtained symbol is a ConstEnum type. Therefore, in the case of const enum EnumD1 { A1 = 1 }, the isConstEnum function determines that EnumD1 is not a ConstEnum type, while in the case of const enum EnumD2 { A1 = 1, B1 = 2 }, the isConstEnum function determines that EnumD2 is a ConstEnum type. So, we want to understand why the types returned by these two enum forms are inconsistent.
// getDeclaredTypeOfEnum
function getDeclaredTypeOfEnum(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (links.declaredType) {
return links.declaredType;
}
if (getEnumKind(symbol) === EnumKind.Literal) {
enumCount++;
const memberTypeList: Type[] = [];
if (symbol.declarations) {
for (const declaration of symbol.declarations) {
if (declaration.kind === SyntaxKind.EnumDeclaration) {
for (const member of (declaration as EnumDeclaration).members) {
const value = getEnumMemberValue(member);
const memberType = getFreshTypeOfLiteralType(getEnumLiteralType(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) {
enumType.flags |= TypeFlags.EnumLiteral;
enumType.symbol = symbol;
}
return links.declaredType = enumType;
}
}
const enumType = createType(TypeFlags.Enum);
enumType.symbol = symbol;
return links.declaredType = enumType;
}
// isConstEnum
export function isConstEnum(sym: Symbol | undefined): boolean {
return !!sym && sym.flags === SymbolFlags.ConstEnum;
}`
// isShareableType
`export function isShareableType(tsType: Type): boolean {
const sym = tsType.getSymbol();
if (isConstEnum(sym)) {
return true;
}
if (tsType.isUnion()) {
return tsType.types.every((elemType) => {
return isShareableType(elemType);
});
}
if (isPurePrimitiveLiteralType(tsType)) {
return true;
}
return isSendableType(tsType);
}
A union type can't have 1 member, so you're not going to see the union flag there. Your code must be able to handle this case.
We want to get the type through the getTypeAtLocation method. Currently, it is getting the type of an enumeration. We expect to get the enumeration type, but in the case of const enum EnumD1 { A1 = 1 }, the actual type obtained is Member. The type obtained from the interface is ambiguous, and there is no switch control for the parameters of the getTypeAtLocation method. If we handle it ourselves, how should we process it to get the actual enumeration type?
A symbol has a declarations list that you can check the forms of.
A runnable sample code would be a lot easier to work with/demonstrate than prose; I'm not sure I fully understand the question.
This issue has been marked as "Not a Defect" and has seen no recent activity. It has been automatically closed for house-keeping purposes.
Thank you for your patient answer!