TypeScript icon indicating copy to clipboard operation
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

Open liyanch opened this issue 5 months ago • 5 comments

🔎 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

liyanch avatar Jun 15 '25 09:06 liyanch

What's the observable manifestation of this difference? SymbolLinks is internal.

RyanCavanaugh avatar Jun 16 '25 17:06 RyanCavanaugh

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);
  }

liyanch avatar Jun 17 '25 04:06 liyanch

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.

RyanCavanaugh avatar Jun 17 '25 17:06 RyanCavanaugh

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?

liyanch avatar Jun 18 '25 03:06 liyanch

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.

RyanCavanaugh avatar Jun 18 '25 16:06 RyanCavanaugh

This issue has been marked as "Not a Defect" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

typescript-bot avatar Jun 21 '25 01:06 typescript-bot

Thank you for your patient answer!

liyanch avatar Jun 21 '25 03:06 liyanch