You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
string enum works from exported strings but not from strings exported as constΒ #59187
// FILE: ./case1.tsexportconstCase1_ImageJpegType='image/jpeg'exportconstCase1_ImagePngType='image/png'// FILE: ./case2.tsexportconstCase2_ImageJpegType='image/jpeg'asconstexportconstCase2_ImagePngType='image/png'asconst// FILE: ./test1.tsimport{Case1_ImageJpegType,Case1_ImagePngType}from'./case1.js'enumCase1{// WORKS AS EXPECTEDPng=Case1_ImagePngType,Jpeg=Case1_ImageJpegType,}// FILE: ./test2.tsimport{Case2_ImageJpegType,Case2_ImagePngType}from'./case2.js'enumCase2{// FAILS WITH: Type 'string' is not assignable to type 'number' as required for computed enum member values.Png=Case2_ImagePngType,Jpeg=Case2_ImageJpegType,}
π Actual behavior
Exporting a string as const breaks string enums.
npx -p typescript@5.5.3 tsc --noEmit -p tsconfig.verbatime-false.json
// FAILS with
// Type 'string' is not assignable to type'number' as required for computed enum member values.
π Expected behavior
Exporting a string with or without as const should not break string enums.
npx -p typescript@5.5.3 tsc --noEmit -p tsconfig.verbatime-false.json
// SHOULD NOT FAIL
Exactly. It is a sneaky difference in the generated .d.ts file. I don't know the exact semantic difference between the two
exportdeclareconstA1='A'exportdeclareconstA2: 'A'
For mere curiosity, aside from the issue explained, I would be glad if someone could point me to a resource/documentation explaining the difference between the two syntaxes.
The difference is basically that a reference to the unannotated one is preferentially string but can act as "A" in context, whereas the other is always just "A". This difference needs to exist for idiomatic code like this:
What I still find strange is the fact that the widened form export declare const A1 = 'A' can be used as value for an enum, while the nonwidening form export declare const A2: 'A' can't be used as enum value.
From what I understand from your explanation and from the article literal-type-widening-in-typescript, it should be the exact opposite or at least a nonwidening form should still be a valid enum value.
I understand the rationale for the difference with respect to how it affects typing now. Is the lack of a literal value in the emit what keeps you from being able to treat it as such? Would it be possible to emit export declare const A2: 'A' = 'A' or export declare const A2 = 'A' as const instead, and if so would that solve the problem?
Since this is "working as intended", I think it needs to be approached as a proposal to change the emit.
Would it be possible to emit export declare const A2: 'A' = 'A' or export declare const A2 = 'A' as const instead, and if so would that solve the problem?
Activity
snarbies commentedon Jul 8, 2024
Looks like the generated typings are different with
as const.export declare const Case1_ImageJpegType = "image/jpeg";versus
export declare const Case2_ImageJpegType: "image/jpeg";Exactly why, I couldn't say, but I'm guessing the problem is related to the lack of an assigned value in the latter.
daniele-orlando commentedon Jul 9, 2024
Exactly. It is a sneaky difference in the generated
.d.tsfile. I don't know the exact semantic difference between the twoFor mere curiosity, aside from the issue explained, I would be glad if someone could point me to a resource/documentation explaining the difference between the two syntaxes.
At the attention of: Anders Hejlsberg (@ahejlsberg)
daniele-orlando commentedon Jul 9, 2024
Ryan Cavanaugh (@RyanCavanaugh)
RyanCavanaugh commentedon Jul 9, 2024
See also https://mariusschulz.com/blog/literal-type-widening-in-typescript
The difference is basically that a reference to the unannotated one is preferentially
stringbut can act as"A"in context, whereas the other is always just"A". This difference needs to exist for idiomatic code like this:vs here with the nonwidening behavior, you get an error:
daniele-orlando commentedon Jul 10, 2024
Thanks Ryan Cavanaugh (@RyanCavanaugh) for the clarification about the two syntaxes.
What I still find strange is the fact that the widened form
export declare const A1 = 'A'can be used as value for an enum, while the nonwidening formexport declare const A2: 'A'can't be used as enum value.From what I understand from your explanation and from the article literal-type-widening-in-typescript, it should be the exact opposite or at least a nonwidening form should still be a valid enum value.
snarbies commentedon Jul 10, 2024
I understand the rationale for the difference with respect to how it affects typing now. Is the lack of a literal value in the emit what keeps you from being able to treat it as such? Would it be possible to emit
export declare const A2: 'A' = 'A'orexport declare const A2 = 'A' as constinstead, and if so would that solve the problem?typescript-bot commentedon Jul 13, 2024
This issue has been marked as "Working as Intended" and has seen no recent activity. It has been automatically closed for house-keeping purposes.
daniele-orlando commentedon Jul 15, 2024
Ryan Cavanaugh (@RyanCavanaugh) please can you reopen the issue? It seams to be an issue with the enum rather than a feature.
Given these two forms
they should be both valid enum values. Especially the second one, which at the moment is treated as invalid and shouldn't.
daniele-orlando commentedon Jul 15, 2024
cc: Wesley Wigham (@weswigham) Jake Bailey (@jakebailey)
snarbies commentedon Jul 15, 2024
Since this is "working as intended", I think it needs to be approached as a proposal to change the emit.
RyanCavanaugh commentedon Jul 15, 2024
Daniele Orlando (@daniele-orlando) can you open a separate issue for that? Thanks
daniele-orlando commentedon Jul 18, 2024
Got it. I'm going to open a proposal issue about it. Thanks for the help.