Skip to content

Add support for representing enums as "const object"s#485

Open
jthacker wants to merge 4 commits intoAleph-Alpha:mainfrom
jthacker:jmt/const-object-enum
Open

Add support for representing enums as "const object"s#485
jthacker wants to merge 4 commits intoAleph-Alpha:mainfrom
jthacker:jmt/const-object-enum

Conversation

@jthacker
Copy link

@jthacker jthacker commented Mar 7, 2026

Goal

This PR introduces a new representation for enums: #[ts(repr(enum = const_object))].

While ts-rs already supports exporting enums as TypeScript enums or type unions, there are many cases where a TypeScript const object is preferred. This approach provides:

  • Runtime access: The values are available as a real object at runtime (unlike type unions).
  • Type safety: A companion type alias is generated using keyof typeof, ensuring the type stays in sync with the object.
  • Flexibility: It supports both integer discriminants and string-based unit variants.

This is a common syntax used in modern TypeScript.
Additionally, as stated in the TypeScript docs the "biggest argument in favour of this format over TypeScript’s enum is that it keeps your codebase aligned with the state of JavaScript."

Changes

Macros

  • Added Repr::ConstObject to the Repr enum to support the new attribute.
  • Updated the attribute parser to recognize const_object in #[ts(repr(enum = ...))].
  • Implemented the TypeScript code generation for the const_object representation in both inline() and decl(). This includes logic to handle implicit and explicit integer discriminants.
  • Updated variant formatting to correctly support the const_object representation, ensuring it falls back to string names for unit variants without discriminants.

Documentation

  • Added documentation for the new const_object representation, outlining its usage and requirements.

Testing

  • Added comprehensive tests covering:
    • Enums with explicit and implicit integer discriminants.
    • String-based unit variants.
    • Integration with rename_all strategies (snake_case, camelCase, kebab-case).
    • Correct inlining behavior.

Other

  • Ran cargo +nightly fmt as required by contributing guide resulting in many changes unrelated to this change so probably a gap in CI

Checklist

  • I have followed the steps listed in the Contributing guide.
  • If necessary, I have added documentation related to the changes made.
  • I have added or updated the tests related to the changes made.

@jthacker jthacker force-pushed the jmt/const-object-enum branch from 97c3975 to 64e0a1e Compare March 7, 2026 16:01
@gustavo-shigueo
Copy link
Collaborator

Hey @jthacker, thanks for the PR, I have a couple of questions:

  • Have you checked that this works properly with export merging (multiple types exported to the same file)?
  • I imagine such enums would be forbidden from having any kind of complex variant such as Foo { x: i32 } or Foo(String), has this been added to the attribute validation?
  • Will the fact that the object and the type have the same name cause issues when importing it?

@jthacker jthacker force-pushed the jmt/const-object-enum branch from 39e8cbf to f9dd66e Compare March 9, 2026 18:24
@jthacker
Copy link
Author

jthacker commented Mar 9, 2026

@gustavo-shigueo Thanks for the feedback! I've answered inline.

Have you checked that this works properly with export merging (multiple types exported to the same file)?

I wasn't aware of this feature, something I'm interested in using too. I have extended the same_file_export.rs integration test in the latest commit. However, these tests appear to not verify the output (just that generation doesn't fail), so let me know if you'd like a more robust test. I manually verified (visually and with tsc) and it looks as I would expect.

I imagine such enums would be forbidden from having any kind of complex variant such as Foo { x: i32 } or Foo(String), has this been added to the attribute validation?

Yes, this is forbidden. This style only works with unit variants. The rust compiler will fail with an error like this All variants of an enum marked as #[ts(repr(enum))] must be unit variants. This is utilizing the existing code path for enum repr. I documented this here.

Will the fact that the object and the type have the same name cause issues when importing it?

It works as expected. If you only want the type, you can import it with import type MyType from .... Otherwise, importing with import MyType from ... gives you access to both. I believe TypeScript keeps these names in separate namespaces and can use the appropriate one as it sees fit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants