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

Enable constants as computed values for string enums #40793

Closed
kataik opened this issue Sep 27, 2020 · 60 comments · Fixed by #50528
Closed

Enable constants as computed values for string enums #40793

kataik opened this issue Sep 27, 2020 · 60 comments · Fixed by #50528
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Fix Available A PR has been opened for this issue Suggestion An idea for TypeScript

Comments

@kataik
Copy link

kataik commented Sep 27, 2020

Search Terms

'TS18033', 'typescript define enum with constant string value', 'string enum computed constant'

Suggestion

Please consider enabling to use constant variables as values for string enums. The following code snippet fails compilation with TS18033 ('Only numeric enums can have computed members, but this expression has type 'string'. If you do not need exhaustiveness checks, consider using an object literal instead.')

const VALUE: string = 'ENUM_VALUE';

enum MyEnum {
  KEY = VALUE, // <-- Fails with TS18033
}

console.log(MyEnum.KEY);

, whereas the following passes:

enum Values {
  VALUE = 'ENUM_VALUE',
}

enum MyEnum {
  KEY = Values.VALUE,
}

console.info(MyEnum.KEY);

Use Cases

Use string interpolation (templated strings) when defining the value of a string enum to be able to reuse pre-defined constants as part of the value. (E.g. namespace prefixes, extension postfixes)

Examples

const NAMESPACE: string = 'com.mycompany.myservice';

enum Errors {
  INVALID_INPUT_ERROR = `${NAMESPACE}.errors#InvalidInput`,
}

Checklist

My suggestion meets these guidelines:

  • [ X ] This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • [ X ] This wouldn't change the runtime behavior of existing JavaScript code
  • [ X ] This could be implemented without emitting different JS based on the types of the expressions
  • [ X ] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • [ X ] This feature would agree with the rest of TypeScript's Design Goals.
@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Sep 28, 2020
@Inlesco
Copy link

Inlesco commented Oct 11, 2020

This would be a perfectly valid feature to implement since computed number values are already allowed.

Is there any reason why computed string values shouldn't be allowed for enums? Perhaps I'm not aware of something tied to the bigger picture?

@RyanCavanaugh can anybody from the TS team comment on my assumptions above? Thanks a lot.

@moneydance
Copy link

moneydance commented Nov 12, 2020

I'm also curious about why computed enums are disabled for strings. Being able to use templated strings would be great. Looks like there was a similar issue here #20440.

@fenduru
Copy link

fenduru commented Apr 16, 2021

This would be handy when when writing a React component with a prop that controls a css class. Right now I'm doing the following

enum ComponentVariant {
  Foo,
  Bar,
}

const classes = {
  [ComponentVariant.Foo]: styles.fooClassName,
  [ComponentVariant.Bar]: styles.barClassName,
}

export default function TheComponent({ variant }: {variant: ComponentVariant}) {
  return <div className={classes[variant]} />
}

But it would be both cleaner, and also safer (i.e. prevent accidentally indexing into a non-existing value in the classes object) to do this directly:

enum ComponentVariant {
  Foo = styles.fooClassName,
  Bar = styles.barClassName,
}

export default function TheComponent({ variant }: {variant: ComponentVariant}) {
  return <div className={variant} />
}

@tomasdev
Copy link

tomasdev commented May 1, 2021

Following the namespace reasoning, highly useful for developing browser extensions or other scripts where we don't want to collision with pre-existing scripts, such as postMessage(<this value>, '*')

@ahnpnl
Copy link

ahnpnl commented Jul 6, 2021

I also need this

@RebeccaStevens
Copy link

Additionally symbols should be allowed to be used as enum values.

enum MyType {
    foo = Symbol("foo"), // Error: ts(18033) - Only numeric enums can have computed members, but this expression has type 'symbol'. If you do not need exhaustiveness checks, consider using an object literal instead.
}

@Thorugoh
Copy link

I'm with the same problem :/

@webia1
Copy link

webia1 commented Nov 29, 2021

please enable, thank you!

@daltonrussell
Copy link

Facing the same issue. I would love to have computed values for strings so I can assign react components to enums.

@SheppardX
Copy link

PUSH!! i am currently facing this issue too.
have an ENUM with endpoints and want to reuse pre-defined parts of that

export const customerEndpoint = '/customers';

export enum ENDPOINTS {
  INVOICES = `${customers}/invoices`,
  BASKET = `${customers}/basket`,
}

as an example

@zheoreh
Copy link

zheoreh commented Jan 19, 2022

+1

@estefafdez
Copy link

I would love to have this on TS, thanks!

@JordanDC2
Copy link

This would be a great feature, currently running into this same issue

@WilliamChazot
Copy link

WilliamChazot commented Mar 11, 2022

I'm currently working to build a enum and interfaces in order to organize my database fields, finally as a custom orm system, and all my database fields have a prefix slot_, that I wanted to specify into string literals in my enum:

// current enum
enum MyTableFields {
  OBJECT_ID = 'slot_objectID',
  CODE = 'slot_code',
  LABEL = 'slot_label'
  // ...and many other fields
}

// wanted enum
const slot_ = 'slot_'
enum MyTableFields {
  OBJECT_ID = `${slot_}objectID`,
  CODE = `${slot_}code`,
  LABEL = `${slot_}label`
}

Apart from that this little formality, I wanted to explore another idea like handling schema or database table names inside enum, like this:

// current enum
enum MyTableFields {
  OBJECT_ID = 'database_name.schema.slot_objectID',
  CODE = 'database_name.schema.slot_code',
  LABEL = 'database_name.schema.slot_label'
  // ...and many other fields
}

// wanted enum
const path = `${databaseName}.${schema}.slot_`
enum MyTableFields {
  OBJECT_ID = `${path}objectID`,
  CODE = `${path}code`,
  LABEL = `${path}label`
}

Enum with computed values could be helpfully helpful !

@MrEmanuel
Copy link

I too want to compute two enums with slight variations, such as a prefix.

@stormBMO
Copy link

+1 here, would be great innovation

@azad-derakhshani-GS
Copy link

+1 from me as well, I would like to assign values to enum variables depending on the environment via the ternary operator.

@iulia-codes
Copy link

I support this as well, any prospects for implementation horizon?

@Labernator
Copy link

+1, this would be quite helpful

@nkappler
Copy link

There's another error message you might get if you try this:
Computed values are not permitted in an enum with string valued members. ts(2553)

@aghArdeshir
Copy link
Contributor

I'm in favor if this change too. I would like to be able to do something like this:

image

@ZEAL-MATCH-LTD
Copy link

This would be ideal! Especially when declaring API route constants.

import { getExact } from "../../util/env";

enum ApiRoutes {
    users = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    blockUser = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    reverifyUser = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    resetPassword = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    preferences = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    pacemakers = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    matchPreferences = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    userLocation = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    tokens = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    messageThread = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    readMessage = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    suggestedSports = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    suggestedGenders = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    findMatch = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    likeUser = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    dislikeUser = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    createP2pReferralCode = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    p2pReferral = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    citiesAPI = 'https://wft-geo-db.p.rapidapi.com/v1/geo/cities',
    discover = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
    eventAttendance = `${getExact('REACT_NATIVE_BACKEND_URL')}/****`,
};

export default apiRoutes;

@GO-DIE
Copy link

GO-DIE commented Jun 20, 2022

I also need this feature
The deletion of this feature causes my code to be difficult to maintain

@embedify
Copy link

+1

@zendesk-olgaczarnecka
Copy link

👋 Hi @ahejlsberg
Will this PR be merged soon ?

@atanas-hristov-deltatre

I'd love to have that feature too.

@goonerify
Copy link

Any update on this and the associated PR?

@ahejlsberg
Copy link
Member

Stay tuned... 😉

@ahejlsberg
Copy link
Member

Now implemented in #50528.

@ahejlsberg ahejlsberg added this to the TypeScript 5.0.0 milestone Oct 17, 2022
@typescript-bot typescript-bot added the Fix Available A PR has been opened for this issue label Oct 17, 2022
@microsoft microsoft deleted a comment from DZ8540 Oct 28, 2022
@ann-aot
Copy link

ann-aot commented Nov 25, 2022

+1

2 similar comments
@rutexd
Copy link

rutexd commented Nov 28, 2022

+1

@vfa-locltb
Copy link

+1

@RyanCavanaugh
Copy link
Member

Why are people +1ing an implemented feature?

@handletastic
Copy link

Why are people +1ing an implemented feature?

Probably because people don't yet know of it? 🤷🏻‍♂️ I mean it's been implemented like a month ago only.

@youthug
Copy link

youthug commented Mar 14, 2023

Hey guys, how to fix this error?

got this in playground (TS version: the latest Nightly):

const enum member initializers must be constant expressions.(2474)

image

and this in IntelliJ IDEA (TS version: 5.0.1-rc)

TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.

@dfl-zeke
Copy link

Why are people +1ing an implemented feature?

It's been a 4-5 months and still not included into latest release, wym.

@RyanCavanaugh
Copy link
Member

5.0 came out yesterday and includes this feature.

@afarah1
Copy link

afarah1 commented Jun 2, 2023

I have a use case like this which still yields TS18033 on 5.0.4:

const fooStr = 'foo';

export const myStrings = {
    foo: fooStr,
};

enum MyEnum {
  FOO = myStrings.foo,
};

@dburgos92
Copy link

On 5.0.4 with angular this doesn't work:

export enum Section{ ASIGN = GlobalConstants.ASIGN DISTRIBUTION = "Distribucion" }

@nick4fake
Copy link

@RyanCavanaugh this is still an issue even with TS 5.1.6:

image

@hanzheliva
Copy link

Screenshot 2023-08-02 at 9 16 12 PM

@RyanCavanaugh Could you please check what the cause of this problem is?

@RyanCavanaugh
Copy link
Member

The error message means what it says.

@AlexKDawson
Copy link

AlexKDawson commented Aug 7, 2023

@RyanCavanaugh I think people (myself included) are still seeing this warning after installing TS >5.0
I'd expect this error to not appear if the feature was implemented?

I felt I should clarify... but I have the same question as @hanzheliva above.

Since const haha = null || "haha" is a constant expression evaluating in typescript as "haha" (not as a string) shouldn't the above code be valid? Or at least be more clear?

I personally think using hard-coded constant expressions (with operators in them) is a bit of a code stink. But I am curious as to how typescript evaluates what is a const expression and what isn't. Are we explicitly not allowed use conditional operators in the declaration of enums even when the expression iteself can be evaluated statically?

@AlexKDawson
Copy link

Side Note: I've seen a couple examples of people trying to do the same with ternary expressions, but the key difference being that typescript still evaluates the following

const foo = true ? 'x' : 'y';

as foo: 'x'|'y' despite that it could technically be evaluated as a const expression foo: 'x'

So for anyone curious, ternary expressions still won't work but at least it makes sense why.

@mgol
Copy link

mgol commented Oct 4, 2023

Issues with using one string enum in another seem like TS bugs; I reported a separate issue for this:
#55978
Please upvote if you hit this, too.

Flagged code, for reference:

enum E1 {
  A = 'A',
}

const enums = {
  E1,
};

enum E2 {
  A = enums.E1.A,
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Fix Available A PR has been opened for this issue Suggestion An idea for TypeScript
Projects
None yet