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
Tuple types are not indexed by number in mapped typesΒ #48398
typeValType="i32"|"i64";classLocal<TypeextendsValType>{readonly #type: Type;constructor(type: Type){this.#type =type;}gettype(): Type{returnthis.#type;}}typeLocals<TypesextendsreadonlyValType[]>={[KinkeyofTypes]: Kextendsnumber ? Local<Types[K]> : Types[K]}functiontoLocals<TypesextendsreadonlyValType[]>(valTypes: Types): Locals<Types>{returnvalTypes.map(type=>newLocal(type))asunknownasLocals<Types>;}consti32x2=["i32","i32"]asconst;// Error, the function should've returned the correct typeconstlocals: readonly[Local<"i32">,Local<"i32">]=toLocals(i32x2);
π Actual behavior
The tuple keys are not mapped correctly as numbers. For some reason K extends number is never triggered for the numeric keys in ["i32", "i32"].
π Expected behavior
The mapping should set K = 0 and K = 1 in the tuple, while it does correctly set them to "0" and "1" as far as I can tell there is no way to identify all "numeric like" strings in a mapped type. My assumption was the whole point of allowing "number" keys for objects was so that this kind've thing worked.
Notes
The mapped types still work fine for array, i.e. Locals<readonly ("i32")[]> resolves just fine to readonly Local<"i32">[] but this functionality doesn't extend to tuples.
typeLocals<Types>={[KinkeyofTypes]: Local<Types[K]>}functiontoLocals<TypesextendsreadonlyValType[]>(valTypes: Types): Locals<Types>{returnvalTypes.map(type=>newLocal(type))asunknownasLocals<Types>;}consti32x2=["i32","i32"]asconst;// This actually works now, but there's a new error aboveconstlocals: readonly[Local<"i32">,Local<"i32">]=toLocals(i32x2);
However it still reports an error in trying to wrap, essentially generics with a constraint can't be used properly in a mapped type:
typeLocals<Types>={// Type 'Types[K]' does not satisfy the constraint 'ValType'.// Type 'Types[keyof Types]' is not assignable to type 'ValType'.// Type 'Types[string] | Types[number] | Types[symbol]' is not assignable to type 'ValType'.// Type 'Types[string]' is not assignable to type 'ValType'.(2344)[KinkeyofTypes]: Local<Types[K]>}
This is the intended behavior. Mapped types can be homomorphic but the keyof operation still uses the string names of the keys (since, even for arrays, the keys are "actually" '0', '1', '2', etc). From a caller perspective, giving you the ability to distinguish between the 0 and "0" meanings doesn't make any sense because objects can't have different property slots.
ability to distinguish between the 0 and "0" meanings
I thought it was more about being able to distinguish between 0 and, say, "foo". That's why numeric index signatures are generally convenient, because it's harder to make that distinction otherwise.
From a caller perspective, giving you the ability to distinguish between the 0 and "0" meanings doesn't make any sense because objects can't have different property slots.
I know that there's not actually "number keys" in JS, but the fact is there is no other way in TypeScript to say K extends IsValidNumberString. Which again is the whole point of allowing number signatures for types in the first place right? Like the fact SomeTupleType[0] is legal works on this presumption.
I thought it was more about being able to distinguish between 0 and, say, "foo". That's why numeric index signatures are generally convenient, because it's harder to make that distinction otherwise.
Arguably with template types, we should be able to doK extends `${ number }` , although at present that doesn't work either:
typeLocals<TypesextendsreadonlyValType[]>={// Type 'Types[K]' does not satisfy the constraint 'ValType'.// Type 'Types[`${number}` & string]' is not assignable to type 'ValType'.[KinkeyofTypes]: Kextends `${ number}` ? Local<Types[K]> : Types[K]}
In this case, K is too wide in the ? case, in practice it could be narrower as K in keyof Types should only include actual keys.
Activity
Jamesernator commentedon Mar 24, 2022
For the record the whole mapped types on tuples and arrays does kind've work:
However it still reports an error in trying to wrap, essentially generics with a constraint can't be used properly in a mapped type:
RyanCavanaugh commentedon Mar 24, 2022
This is the intended behavior. Mapped types can be homomorphic but the keyof operation still uses the string names of the keys (since, even for arrays, the keys are "actually" '0', '1', '2', etc). From a caller perspective, giving you the ability to distinguish between the 0 and "0" meanings doesn't make any sense because objects can't have different property slots.
fatcerberus commentedon Mar 24, 2022
I thought it was more about being able to distinguish between 0 and, say, "foo". That's why numeric index signatures are generally convenient, because it's harder to make that distinction otherwise.
Jamesernator commentedon Mar 25, 2022
I know that there's not actually "number keys" in JS, but the fact is there is no other way in TypeScript to say
K extends IsValidNumberString. Which again is the whole point of allowingnumbersignatures for types in the first place right? Like the factSomeTupleType[0]is legal works on this presumption.Arguably with template types, we should be able to do
K extends `${ number }`, although at present that doesn't work either:In this case, K is too wide in the
?case, in practice it could be narrower asK in keyof Typesshould only include actual keys.Although I suppose we can do this:
This is definitely "correct", but man is it a long and nuanced type to write just to map the usual keys of an array/tuple.
Like TypeScript already offers the magic mapped array/tuple types behaviour, it would probably be simpler if this just worked for generics with constraints as well as per my second comment.
Because for whatever reason adding the constraint:
Means the mapped type no longer acts as a array/tuple-only mapped type.
Alternatively alternatively, we could just have actual syntax to say some mapped type just applies to the entries in an array/tuple:
RyanCavanaugh commentedon Mar 25, 2022
An operator to get the numeric keys out of a type, in number form, would be a reasonable suggestion
jcalz commentedon Mar 27, 2022
Like #48094?
typescript-bot commentedon Mar 29, 2022
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
ahejlsberg commentedon Apr 26, 2022
This issue was fixed by #48837. The original example now works as expected when
Localsis simplified to just this: