Tuple types are not indexed by number in mapped types #48398
Copy link
Copy link
Closed
Labels
FixedA PR has been merged for this issueA PR has been merged for this issueWorking as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bug
Description
Bug Report
🔎 Search Terms
tuple, mapped types, number
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about tuple types
⏯ Playground Link
Playground link with relevant code
💻 Code
type ValType = "i32" | "i64";
class Local<Type extends ValType> {
readonly #type: Type;
constructor(type: Type) {
this.#type = type;
}
get type(): Type {
return this.#type;
}
}
type Locals<Types extends readonly ValType[]> = {
[K in keyof Types]: K extends number ? Local<Types[K]> : Types[K]
}
function toLocals<Types extends readonly ValType[]>(valTypes: Types): Locals<Types> {
return valTypes.map(type => new Local(type)) as unknown as Locals<Types>;
}
const i32x2 = ["i32", "i32"] as const;
// Error, the function should've returned the correct type
const locals: 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.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
FixedA PR has been merged for this issueA PR has been merged for this issueWorking as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bug
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: