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
Can't index object in generic function with property name extracted with Pick from derived type #40832
interfaceIRecord{id: number,name: string}typeFilterProperties<T>=Pick<T,{[KinkeyofT]: T[K]extendsstring ? K : never}[keyofT]>;functionextract<T>(data: T[],field: keyofFilterProperties<T>): void{constrecord: T=data[0];constvalue: string=record[field];// see error below}constdata: IRecord[]=[{id: 1,name: "one"},{id: 2,name: "two"}]extract<IRecord>(data,"id");// expected: error that id can't be used as a parameter since it's not a stringextract<IRecord>(data,"name")// no error, expected behaviour
Expected behavior:
I'm trying to create a function that receives an object/an array of objects of type T and a field name but the user should be able to call the function only if the type of that field in T is string. FilterProperties is just a type (union) that picks those properties from T and can be easily extended to filter any kind of properties.
Since I was able to call the function correctly on the last line (because name is a field of type string, I was expecting to be able to extract the value in the function too, but I get the error below. Everything works if you define everything explicitly (no generics).
Actual behavior:
Type 'T[{ [K in keyof T]: T[K] extends string ? K : never; }[keyof T]]' is not assignable to type 'string'.
Type 'T[T[keyof T] extends string ? keyof T : never]' is not assignable to type 'string'.
Type 'T[keyof T]' is not assignable to type 'string'.
Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'string'.
Type 'T[string]' is not assignable to type 'string'.ts(2322)
You're assuming that T extends string means "T is a string", but that's not the case. It just extends string, but it can be a more narrow literal string type, e.g. 'foo'. Trying to assign 'foo' to string is not allowed.
edit: It was a long day and this was completely wrong.
The problem here is that we don't have the higher-order reasoning to say "If a key came from an operation that filters out keys that aren't strings, then indexing by that key must have produced a string". The FilterProperties type is effectively logically opaque from TS's point of view.
An alternate way of writing the function that achieves the desired semantics would be:
interfaceIRecord{id: number,name: string}functionextract<Kextendsstring>(data: Record<K,string>[],field: K): void{constvalue: string=data[0][field];// see error below}constdata: IRecord[]=[{id: 1,name: "one"},{id: 2,name: "two"}]extract(data,"id");// error that id can't be used as a parameter since it's not a stringextract(data,"name")// no error, expected behaviour
Activity
MartinJohns commentedon Sep 29, 2020
You're assuming thatT extends stringmeans "Tis astring", but that's not the case. It just extendsstring, but it can be a more narrow literal string type, e.g.'foo'. Trying to assign'foo'tostringis not allowed.edit: It was a long day and this was completely wrong.
RyanCavanaugh commentedon Sep 29, 2020
Martin Johns (@MartinJohns) that's a valid subtyping
The problem here is that we don't have the higher-order reasoning to say "If a key came from an operation that filters out keys that aren't strings, then indexing by that key must have produced a string". The
FilterPropertiestype is effectively logically opaque from TS's point of view.An alternate way of writing the function that achieves the desired semantics would be: