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
Object.values(Enum) should return an array of Enum's value's type #33200
enumRole{Admin='ADMIN',Results='RESULTS',Finish='FINISH',Config='CONFIG'}Object.values(Role).forEach(role=>{console.log(typeofrole);// prints 'string' four times});Object.values(Role).includes('hello');// errors at build time
Expected behavior:Object.values(EnumType) at build time should be returning an array of the same type as the values of the EnumType.
Actual behavior:Object.values(EnumType).includes('enumValueType') errors out with
error TS2345: Argument of type '"enumValueType"' is not assignable to parameter of type 'EnumType'.
It appears as though, at build time, Object.values(EnumType) is parsed as returning an array of EnumTypes.
Error first encountered on Typescript 3.6.2. Still present as of typescript@next.
Playground Link:Playground link. Playground does not appear to be correctly using ES2017 and Typescript 3.6.2/3.7.0 are not available.
The error looks reasonable to me. Why are you checking for 'hello' instead of, say, Role.Config? If you check Object.values(Role).includes(Role.Config) there's no error.
Maybe you're not aware that the type named Role is not the type of the value named Role ? The value Role that exists at runtime is a mapping from keys to values, while the type named Role is the union of the value types. So I expect Object.values(Role) to produce an Array<Role>, which it does. That is, your "expected behavior" is what is actually happening.
Our real-world use case here is that the input to includes comes from user input.
Object.values should be returning an array of the values contained on that object. The value of Role.Config should be the string CONFIG, so there should be no error.
Perhaps more importantly, prior to 3.6.2, this functionality worked exactly as expected. It was the (supposedly non-breaking, were semver followed) update that broke this purely at build time.
Reacted by Paul Chaplin, Ersin Akinci, honza, EVRUSIN, Craig C., John Munson, David Furlong, Wes Roberts, Ayushi Sharma and MaximeEsnol
interfaceObjectConstructor{values<T>(o: {[s: string]: T}|ArrayLike<T>): T[];// first overloadvalues(o: {}): any[];// second overload}
In TS3.5 and below, Object.values(Role) couldn't see Role as an indexable type, so it invoked the second overload signature above and returned an Array<any>, at which point all type safety on the array elements was lost.
But TS3.6 included #31687, allowing enum objects to be seen as implicitly having index signatures. So, starting in TS3.6, enum Role {...} is now assignable to a value of type {[k: string]: Role}. This is probably exactly what you want. And this now allows Object.values(Role) to invoke the first overload signature above and return Array<Role>, which is also probably exactly what you want.
That means in TS3.5 the compiler happily allowed you to call Object.values(Role).includes("randomThing") as well as Object.values(Role).includes(12345) and Object.values(Role).includes({name: "teapot", size: "little", height: "short", width: "stout"}). That's really too permissive; it probably should be an error to look for pottery in an array of enum values.
Starting in TS3.6, you are now forced to deal with the fact that Array<T>.includes(val: T) accepts a T and not something wider. In this case, it wants you to check a Role and not a string. Think that's too restrictive? That was brought up as #32201 and there are reasons and workarounds mentioned and linked in there
The short answer here is you should probably widen Object.values(Role) to string[] before doing includes() on some random string, if that's what you want (e.g., (Object.values(Role) as string[]).includes("randomString")).
I really don't think what we are asking for is particularly complex or confusing. At runtime, Object.values(Role) returns an array of strings, because Role is an enum where each of the entries has a value of a string. We would like the build time to respect that. We have defined an enum with string values, so Object.values should be treated as an array of strings, or at the very least should not break when accessed as such.
In TS3.5 and below, Object.values(Role) couldn't see Role as an indexable type, so it invoked the second overload signature above and returned an Array, at which point all type safety on the array elements was lost.
This is certainly disappointing, and it is nice to see an attempt to tighten things up a bit. But unfortunately they have not been tightened up correctly.
this now allows Object.values(Role) to invoke the first overload signature above and return Array, which is also probably exactly what you want
No, we want Object.values(Role) to return an array of the values contained within each Role. Very explicitly not Array<Role>. That's what the "values" in Object.values is referring to. Role.Admin being one instance of Role, and 'ADMIN' being the value of that Role.
We would also like it if breaking changes were not made in a minor version update. If this breaking change is absolutely necessary, it should be made with TypeScript version 4.x, not in a minor version update. It's very notable that according to Dependabot, this version update saw an over 10% failure rate, despite being a minor update, when most other builds have less than 3%. Heck, even the 2.9.2 → 3.0.1 breaking change had a lower rate of failure than this one! Surely Microsoft can not consider that acceptable.
Reacted by Paul Chaplin, Vlad Tokarev, Kim Hildeqvist, Ersin Akinci, honza, Tom Johnson, Craig C., Ian Copp, John Munson, northernbird and 5 more
No, we want Object.values(Role) to return an array of the values contained within each Role. Very explicitly not Array<Role>
Except that's exactly what Array<Role>is. Role is, in practice, a union of the possible values of the Role enum, which can be seen here:
enumRole{Admin='ADMIN',Results='RESULTS',Finish='FINISH',Config='CONFIG'}declareletx: Role;lety: 'ADMIN'|'RESULTS'|'FINISH'|'CONFIG'=x;// ok
You wouldn't argue that if you had an array of type number[], you should be able to go .includes("foo"), right? That's obviously a category error. So your actual pain point comes from the fact that TS has literal types in addition to the standard primitives. Since Array is generic, how could you decide which uses of T in that type should be widened (if they're literals) vs. ones that shouldn't? There are cases where widening the generic type wouldn't be desirable, so TS is conservative and doesn't do it.
tl;dr: TS knows statically at compile time the exact string values contained in Role-the-object, so it gives you an Array<"ADMIN"|"RESULTS"|"FINISH"|"CONFIG">, or Array<Role> for short. You then get stung because literal types can't safely be widened in a generic context.
We would also like it if breaking changes were not made in a minor version update. If this breaking change is absolutely necessary, it should be made with TypeScript version 4.x, not in a minor version update.
For better or worse, TS doesn't do semantic versioning; see #31761
To reiterate: this all seems to be working as intended. You should change your code to something like
enumRole{Admin='ADMIN',Results='RESULTS',Finish='FINISH',Config='CONFIG'}constallRoles: string[]=Object.values(Role);// this is what I want, compiler!allRoles.forEach(role=>{console.log(typeofrole);});allRoles.includes('hello');
which communicates your intent to the compiler (allRoles should be a string[] and not an any[] or a Role[]), and works in both TS3.5 and TS3.6. I'll stop spamming contributing to this thread now and wait for official word from a TS repo maintainer. Good luck!
Reacted by Pasha Nemchenko, Denis Marusevich, xw1097, Oskar Hane and Ralph F
changed the title [-]Object.values(Enum).includes(EnumValue) errors at build time[/-][+]Object.values(Enum) should return an array of Enum's value's type[/+]on Sep 3, 2019
The original PR suggests a workaround: Since numeric enums use older, looser rules than string enums, you can get close to the old behaviour by adding an entry to Role like Dummy = 0 (or your favourite number).
However, anytime something comes from user input, you should expect to assert or narrow its type before the compiler understands it. That code will necessarily involve some bending of the type system since the checks are runtime, but the errors you're getting are at compile-time. For 3.6, your checks that strings from user input are actually members of an enum now need associated assertions to make the type system leave you alone until the check is done. I would personally write it as
Object.values(Role).includes('hello'asRole)
Reacted by chulanovskyi-bs, Paul Chaplin, Max Hübner, Jess Solka, Ilya Markov, Ryan Pivovar, Son Sumin, Seth Killian, buriymishka, Jakson Xavier and 12 more
Activity
jcalz commentedon Sep 3, 2019
The error looks reasonable to me. Why are you checking for
'hello'instead of, say,Role.Config? If you checkObject.values(Role).includes(Role.Config)there's no error.Maybe you're not aware that the type named
Roleis not the type of the value namedRole? The valueRolethat exists at runtime is a mapping from keys to values, while the type namedRoleis the union of the value types. So I expectObject.values(Role)to produce anArray<Role>, which it does. That is, your "expected behavior" is what is actually happening.Lauraducky commentedon Sep 3, 2019
Our real-world use case here is that the input to
includescomes from user input.Object.valuesshould be returning an array of the values contained on that object. The value ofRole.Configshould be the stringCONFIG, so there should be no error.Perhaps more importantly, prior to 3.6.2, this functionality worked exactly as expected. It was the (supposedly non-breaking, were semver followed) update that broke this purely at build time.
jcalz commentedon Sep 3, 2019
Here's the relevant type definitions for
Object.values():In TS3.5 and below,
Object.values(Role)couldn't seeRoleas an indexable type, so it invoked the second overload signature above and returned anArray<any>, at which point all type safety on the array elements was lost.But TS3.6 included #31687, allowing enum objects to be seen as implicitly having index signatures. So, starting in TS3.6,
enum Role {...}is now assignable to a value of type{[k: string]: Role}. This is probably exactly what you want. And this now allowsObject.values(Role)to invoke the first overload signature above and returnArray<Role>, which is also probably exactly what you want.That means in TS3.5 the compiler happily allowed you to call
Object.values(Role).includes("randomThing")as well asObject.values(Role).includes(12345)andObject.values(Role).includes({name: "teapot", size: "little", height: "short", width: "stout"}). That's really too permissive; it probably should be an error to look for pottery in an array of enum values.Starting in TS3.6, you are now forced to deal with the fact that
Array<T>.includes(val: T)accepts aTand not something wider. In this case, it wants you to check aRoleand not astring. Think that's too restrictive? That was brought up as #32201 and there are reasons and workarounds mentioned and linked in thereThe short answer here is you should probably widen
Object.values(Role)tostring[]before doingincludes()on some randomstring, if that's what you want (e.g.,(Object.values(Role) as string[]).includes("randomString")).jimcullenaus commentedon Sep 3, 2019
I really don't think what we are asking for is particularly complex or confusing. At runtime,
Object.values(Role)returns an array of strings, becauseRoleis an enum where each of the entries has a value of a string. We would like the build time to respect that. We have defined an enum with string values, soObject.valuesshould be treated as an array of strings, or at the very least should not break when accessed as such.This is certainly disappointing, and it is nice to see an attempt to tighten things up a bit. But unfortunately they have not been tightened up correctly.
No, we want
Object.values(Role)to return an array of the values contained within eachRole. Very explicitly notArray<Role>. That's what the "values" inObject.valuesis referring to.Role.Adminbeing one instance ofRole, and'ADMIN'being the value of thatRole.We would also like it if breaking changes were not made in a minor version update. If this breaking change is absolutely necessary, it should be made with TypeScript version 4.x, not in a minor version update. It's very notable that according to Dependabot, this version update saw an over 10% failure rate, despite being a minor update, when most other builds have less than 3%. Heck, even the 2.9.2 → 3.0.1 breaking change had a lower rate of failure than this one! Surely Microsoft can not consider that acceptable.
fatcerberus commentedon Sep 3, 2019
Except that's exactly what
Array<Role>is.Roleis, in practice, a union of the possible values of theRoleenum, which can be seen here:You wouldn't argue that if you had an array of type
number[], you should be able to go.includes("foo"), right? That's obviously a category error. So your actual pain point comes from the fact that TS has literal types in addition to the standard primitives. SinceArrayis generic, how could you decide which uses ofTin that type should be widened (if they're literals) vs. ones that shouldn't? There are cases where widening the generic type wouldn't be desirable, so TS is conservative and doesn't do it.tl;dr: TS knows statically at compile time the exact string values contained in
Role-the-object, so it gives you anArray<"ADMIN"|"RESULTS"|"FINISH"|"CONFIG">, orArray<Role>for short. You then get stung because literal types can't safely be widened in a generic context.jcalz commentedon Sep 3, 2019
For better or worse, TS doesn't do semantic versioning; see #31761
jcalz commentedon Sep 3, 2019
To reiterate: this all seems to be working as intended. You should change your code to something like
Playground link
which communicates your intent to the compiler (
allRolesshould be astring[]and not anany[]or aRole[]), and works in both TS3.5 and TS3.6. I'll stopspammingcontributing to this thread now and wait for official word from a TS repo maintainer. Good luck![-]Object.values(Enum).includes(EnumValue) errors at build time[/-][+]Object.values(Enum) should return an array of Enum's value's type[/+]sandersn commentedon Sep 3, 2019
The original PR suggests a workaround: Since numeric enums use older, looser rules than string enums, you can get close to the old behaviour by adding an entry to
RolelikeDummy = 0(or your favourite number).However, anytime something comes from user input, you should expect to assert or narrow its type before the compiler understands it. That code will necessarily involve some bending of the type system since the checks are runtime, but the errors you're getting are at compile-time. For 3.6, your checks that strings from user input are actually members of an enum now need associated assertions to make the type system leave you alone until the check is done. I would personally write it as
typescript-bot commentedon Sep 6, 2019
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
typescript-bot commentedon Sep 9, 2019
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
typescript-bot commentedon Sep 12, 2019
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
Update lib.es2016.array.include.d.ts