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
Using const string enum as object key produces an indexed type #16687
constenumTest{A='a',B='b'}typeTestMap={[keyinTest]: string}// Type '{ [x: string]: string; }' is not assignable to type 'TestMap'.// Property 'a' is missing in type '{ [x: string]: string; }'.'constx: TestMap={[Test.A]: 'string',[Test.B]: 'another string'}
Expected behavior:
Keys from a string enum to be usable as static key names, similar to how constant strings are usable in computed key position. This probably should work even with non-const enums.
constx: TestMap={// currently works['a']: 'string',['b']: 'another string'}
Actual behavior:
Not usable without a (potentially incorrect?) type assertion.
[ts] Type '{ [x: string]: Value; }' is not assignable to type 'Record<Name, Value>'.
[ts] A computed property name must be of type 'string', 'number', 'symbol', or 'any'.
Simon Buchan (@simonbuchan) Not really. @Kovensky wants the keys of strings enums to be statically known for use in computed properties.
You want one unknown member of an unknown set of keys to be create a type with computed name in it. Something like { [Name]: Value }, with [Name] to be assignable to other uses of T[Name] or something like that. (I'm haven't worked out in detail how it would have to work.)
Unfortunately, Typescript doesn't have a type like this, though, so instead the type inferred for the object literal has a string indexer, which is not assignable to a mapped type, even though both have an unknown set of allowed properties. You'll have to read the PRs for mapped types, but I think the reason is that a string indexer will always have an unknown set of properties, but the mapped type, when instantiated, will have a particular set of properties. For example:
make('hi','there'): Record<'hi',string>
And { [x: string]: Value } should not be assignable to that since it might not have the property 'hi'.
Nathan Shively-Sanders (@sandersn) I think I get it reflecting on this a bit: Record<Key, Value> is { [key in Key]: Value }, not { [key: Key]: Value }, because the latter doesn't make sense when Key is not string (or any or never) or a string literal type (and this issue is that string enums should be considered the latter?). Is it correct that the only other type Key could be for Key extends string is a union of the previous? Would it make sense if TS had something like literal string as a type?
functionmake<Nameextendsliteralstring,Value>(name: Name,value: Value): {[name: Name]: Value};make("foo","bar");// { foo: string } - though I would like { foo: "bar" }, that's another issue["foo","bar"].map(name=>make(name,name));// either error on make "'string' is not assignable to 'literal string' or Array<{ [name: string]: string }> as before?functionzipMake<Namesextendsstring,Values>(names: Names[],values,Values[]){returnnames.map((name,i)=>make(name,values[i]));// "'Names' is not assignable to 'literal string'"}functionzipMake<Namesextendsliteralstring,Values>(names: Names[],values,Values[]);// Either error or can only be called with 0 or 1-length literal array of `names` (or matching constrained type)?
This arose when I was playing around with a toy version of recompose, the use case is essentially that withState("counter", "setCounter", 0) should return a function that takes wraps a react component and adds the counter and setCounter props`: when simplified, something like:
I could get it to typecheck pretty well externally with ExternalProps & Record<Name, Value> & Record<UpdaterName, (value: Value) => void>, but I see now it would break in weird ways with type unions.
Simon Buchan (@simonbuchan) Do you mind creating a separate issue for this with your example code and discussion? This feels like it is generating discussion distinct from that based on the original example. I'll see if I can answer your questions there.
Nathan Shively-Sanders (@sandersn) Seems like it's #2049 in this specific case. I've been thinking about the general case and working on a proper proposal - turns out languages are hard :). Further discussion would be in #2049 or a proposal.
Mohamed Hegazy (@mhegazy), with that code, won't a k : "foo" | "bar" generate { foo: number, bar: number }? It should instead be { foo: number } | { bar: number }, or equivalent.
Edit: I'm not really clear on what the [Q in P] is doing, I guess: is it possible for P to be a union type when it's already the index of keyof T? Current VS Code claims { foo, bar } like I said, changing [Q in P] to just [P] just turns it into {} (which seems like a bug), but it looks like that might be what the fix allows.
Edit Edit: looks like TS 2.5.2 correctly reports an error on [P].
Edit 3: OK, tried on nightlies, and it looks like with #18317Unionize<T> works as expected (nice!) Looks like I will need a pretty careful comment though!
I would like this feature to work too, but I have a couple of issues
I would like to construct an object with any number of values from the enum, but I'm forced to provide an object for every one:
exportconstenumStepNames{
one ='one',
two ='two',
final ='final'}exporttypeStep={[keyinStepNames]: {header: string}};
When I try to use it like:
constoneStep: Step={'one': {header: 'First'}};
I get this compile error:
TS2322: Type '{ 'one': { header: string; }; }' is not assignable to type 'Step'.
Property 'two' is missing in type '{ 'one': { header: string; }; }'.
I would like to parameterize my Step type, then I can pass in any set of step names, but I get a strange error message. Here's an example extending from the OP's.
constenumTest{
A ='a',
B ='b'}typeTestMap<T>={[keyinkeyofT]: string}constx: TestMap<Test> = {['a']: 'string',['b']: 'another string'}
// Error: TS2322: Type '{['a']: string;['b']: string;}' is not assignable to type 'Test'.
Activity
SLaks commentedon Jul 10, 2017
This should also work for numeric enums.
simonbuchan commentedon Jul 31, 2017
Is this the same error?
Gives:
sandersn commentedon Aug 2, 2017
Simon Buchan (@simonbuchan) Not really. @Kovensky wants the keys of strings enums to be statically known for use in computed properties.
You want one unknown member of an unknown set of keys to be create a type with computed name in it. Something like
{ [Name]: Value }, with[Name]to be assignable to other uses ofT[Name]or something like that. (I'm haven't worked out in detail how it would have to work.)Unfortunately, Typescript doesn't have a type like this, though, so instead the type inferred for the object literal has a string indexer, which is not assignable to a mapped type, even though both have an unknown set of allowed properties. You'll have to read the PRs for mapped types, but I think the reason is that a string indexer will always have an unknown set of properties, but the mapped type, when instantiated, will have a particular set of properties. For example:
And
{ [x: string]: Value }should not be assignable to that since it might not have the property 'hi'.simonbuchan commentedon Aug 2, 2017
Nathan Shively-Sanders (@sandersn) I think I get it reflecting on this a bit:
Record<Key, Value>is{ [key in Key]: Value }, not{ [key: Key]: Value }, because the latter doesn't make sense whenKeyis notstring(oranyornever) or a string literal type (and this issue is that string enums should be considered the latter?). Is it correct that the only other typeKeycould be forKey extends stringis a union of the previous? Would it make sense if TS had something likeliteral stringas a type?This arose when I was playing around with a toy version of
recompose, the use case is essentially thatwithState("counter", "setCounter", 0)should return a function that takes wraps a react component and adds thecounterandsetCounterprops`: when simplified, something like:I could get it to typecheck pretty well externally with
ExternalProps & Record<Name, Value> & Record<UpdaterName, (value: Value) => void>, but I see now it would break in weird ways with type unions.sandersn commentedon Aug 3, 2017
Simon Buchan (@simonbuchan) Do you mind creating a separate issue for this with your example code and discussion? This feels like it is generating discussion distinct from that based on the original example. I'll see if I can answer your questions there.
simonbuchan commentedon Aug 8, 2017
Nathan Shively-Sanders (@sandersn) Seems like it's #2049 in this specific case. I've been thinking about the general case and working on a proper proposal - turns out languages are hard :). Further discussion would be in #2049 or a proposal.
mhegazy commentedon Aug 22, 2017
another interesting use case described in #17784
sandersn commentedon Aug 24, 2017
#15473 should be to fix this.
mhegazy commentedon Sep 8, 2017
Note that this does not cover the generic case, i.e.
for these cases we recommend casting to
unionizetype that generates a union of all properties of the object, i.e.:simonbuchan commentedon Sep 8, 2017
Mohamed Hegazy (@mhegazy), with that code, won't ak : "foo" | "bar"generate{ foo: number, bar: number }? It should instead be{ foo: number } | { bar: number }, or equivalent.Edit: I'm not really clear on what the[Q in P]is doing, I guess: is it possible forPto be a union type when it's already the index ofkeyof T? Current VS Code claims{ foo, bar }like I said, changing[Q in P]to just[P]just turns it into{}(which seems like a bug), but it looks like that might be what the fix allows.Edit Edit: looks like TS 2.5.2 correctly reports an error on[P].Edit 3: OK, tried on nightlies, and it looks like with #18317
Unionize<T>works as expected (nice!) Looks like I will need a pretty careful comment though!kimwykoff commentedon Apr 16, 2018
I would like this feature to work too, but I have a couple of issues
When I try to use it like:
I get this compile error:
Is there any way to get around this problem?
SLaks commentedon Apr 16, 2018
kimwykoff
You need to mark those properties as optional. Demo
Since you're using values (not the enum property names), you need to specify
T, notkeyof T. Demokimwykoff commentedon Apr 16, 2018
SLaks (@SLaks) Thanks. Sorry for the basic questions.