Repository navigation
Mapped array/tuple types can't be used as generic with a constraint #48740
Description
Activity
I think it is explained in #48398.
For you example, a workaround is replaceLocal<Type[K]>withLocal<Type[K & number]>Jamesernator commented
on Apr 17, 2022 AuthorMore actionsI think it is explained in #48398.
The fact tuple keys aren't subtypes of
numberis explained, however that is a distinct issue. Here the problem is thatKwill only be the "numeric like" types (although note from that issue that for tuples,Kis never assignable tonumber, they are exposed as string types only).For you example, a workaround is replace
Local<Type[K]>withLocal<Type[K & number]>No, that doesn't work, it causes the final type in the example to become:
const locals: [Local<I32 | Externref>, Local<I32 | Externref>]
Which is not correct, the existing
[Local<I32>, Local<Externref>]is correct already actually, it's just that it warns in the mapped type despite the fact it produced the correct type anyway.No, that doesn't work
You are right.
Local<Type[K & number]>doesn't work.Since what we need is only 'numeric like' types in
keyof T, I believe it is necessary to introduce a generic typeKeyOf<T>to provide only numeric index types for array/tuple, which should be in a form of0 | 1 | 2 ... | N-1whenTis a N-length tuple, and should benumberwhenTis an unknown-length array.
It could be written as below.type _GetKeysFromLength< L extends number, R extends readonly unknown[] = [], > = number extends L ? number : R['length'] extends L ? R[number] : _GetKeysFromLength<L, readonly [R['length'], ...R]>; type KeyOf<T extends unknown[]> = _GetKeysFromLength<T['length']> extends infer U extends number ? U : never; // for versions prior to v4.7 // type KeyOf<T extends unknown[]> = _GetKeysFromLength<T['length']> extends infer U ? U extends number ? U : never : never; type R1 = KeyOf<[1,2,3]>; // ^? type R1 = 0 | 2 | 1 type R2 = KeyOf<number[]>; // ^? type R2 = number type LocalsFor<Type extends Array<ValType>> = { [K in KeyOf<Type>]: Local<Type[K]> }; const locals: LocalsFor<[I32, Externref]> = [ new Local(new I32()), new Local(new Externref()), ]; // seems OK now! var l0 = locals[0] // ^? var l0: Local<I32> var l1 = locals[1] // ^? var l1: Local<Externref>
RyanCavanaugh commented
on Apr 18, 2022 MemberMore actionsThis is a correct error.
In this particular case, we have an explicit Type extends ReadonlyArray so TypeScript should be able to deduce just fine that this particular mapped type will only ever be over an array, and hence should be able to deduce that...
This is the incorrect assumption.
Matching the constraint
extends ReadonlyArray<ValType>does not provide a bound on an arbitraryType[keyof Type], as shown:type M = ReadonlyArray<ValType> & { k: boolean }; type LocalsFor<Type extends ReadonlyArray<ValType>> = { // Type 'Type[K]' does not satisfy the constraint 'ValType'. // Type 'Type[keyof Type]' is not assignable to type 'ValType'. // Type 'Type[string] | Type[number] | Type[symbol]' is not assignable to type 'ValType'. // Type 'Type[string]' is not assignable to type 'ValType'.(2344) [K in keyof Type]: Local<Type[K]> }; type G = LocalsFor<M>; type X = G["k"]; // x: Local<boolean>, a constraint violation
- addedWorking as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bug
on Apr 18, 2022 Jamesernator commented
on Apr 18, 2022 AuthorMore actionsMatching the constraint
extends ReadonlyArray<ValType>does not provide a bound on an arbitraryType[keyof Type], as shown:These bizarre rules make the whole point of "homomorphic mapped types" seem fairly weak and unreliable, like if there's gonna be special casing for arrays in mapped types to not map stuff like methods, why would we want it to map other random properties that might be attached onto the array? (as an example, symbol-keyed protocols like
Symbol.for("nodejs.util.inspect.custom")and such, people probably wouldn't want such properties suddenly appear in mapped types if no other methods do)The amount of bizarre behaviours in the mapped types feature just constantly surprises me, I don't see how any developers who aren't intimately familiar with TypeScript's internals are expected to have any reasoning about these complicated features (and it doesn't help that that handbook doesn't even mention arrays are (kind've) homomorphic in mapped types to begin with, this was something just mentioned once when the feature was released in the associated release blog post).
Jamesernator commented
on Apr 18, 2022 AuthorMore actionsEither way, I think I'll reopen the other issue because that one at the very least checks the keys for "numeric"-ness.
RyanCavanaugh commented
on Apr 18, 2022 MemberMore actionsThe amount of bizarre behaviours in the mapped types feature just constantly surprises me
Every single rule we've added to mapped types was put there to remove some other surprise 🤷♂️
So is #27995 not really a bug then?
This has changed from "working as intended" to "fixed" by #48837. Not sure if anyone wants to alter tags here.
- addedFixedA PR has been merged for this issueA PR has been merged for this issueand removedWorking as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bug
on Apr 27, 2022
Bug Report
🔎 Search Terms
mapped tuple type
🕗 Version & Regression Information
⏯ Playground Link
Playground link with relevant code
💻 Code
🙁 Actual behavior
It produces an error in the mapped type that
Type[K]doesn't satisfy the constraint.🙂 Expected behavior
There should be no error, as mapped types do not map over all keys in the array/tuple, it should only consider
Type[K]to be the numeric keys of the tuple, i.e.Type[K]in the mapped type will only ever be (subtypes of)ValTypebecause ultimatelyKwill only be assigned to the numeric keys ofTypeas it's an array type.In this particular case, we have an explicit
Type extends ReadonlyArray<ValType>so TypeScript should be able to deduce just fine that this particular mapped type will only ever be over an array, and hence should be able to deduce thatType[K]will only ever beType[number](orType[`${ number }`]for tuples).I had another issue, however I closed it and opened this one because this is likely the fundamental cause of both problems.