Repository navigation
generic bound doesn't work when used in index type with concrete index typeย #51394
Description
Activity
This is similar to #30581 and as such the current non-type-assertion solution is to refactor as mentioned in #47109, but the cure might be worse than the disease.
So, TS is protecting you from doing this (unlikely in practice) terrible thing:
const setValue = <K extends keyof Foo>(key: K, value: Foo[K][number]) => { foo[key] = [value] // compiler error } setValue(Math.random() < 0.999 ? "a" : "b", "oopsie"); // no compiler error foo.a.map(x => x.toFixed()) // 99.9% chance of runtime error
Of course that same problem can happen in the version that "works":
const setValue = <K extends keyof Foo>(key: K, value: Foo[K]) => { foo[key] = value // no compiler error } setValue(Math.random() < 0.999 ? "a" : "b", ["oopsie"]); // no compiler error foo.a.map(x => x.toFixed(2)) // 99.9% chance of runtime error
But, as described in #30769 (comment)
One rule we've always had is that any given type (and, by extension, any
T[K]for identicalTandK) is by definition assignable to itself, and that's the basic unsoundness we permit.
So assignmentlhs = rhsis allowed iftypeof lhsandtypeof rhsare considered identical by the compiler, even if it would be unsound to allow it.The version that works has identical
Foo[K]on both sides, but the one that fails hasFoo[K]on the left andFoo[K][number][]on the right, so they are not identical. You could rewrite your types as described in #47109 so that the compiler sees the assignment as being identical types on both sides, which involves multiple reassignments (but no type assertions):type _Foo<K extends keyof Foo = keyof Foo> = { [P in K]: Foo[P][number][] }; const _foo: _Foo = foo; // no compiler error const setValue = <K extends keyof Foo>(key: K, value: Foo[K][number]) => { const __foo: _Foo<K> = _foo; // no compiler error __foo[key] = [value]; // no compiler error }
But, uh, all that to work around stuff so that it permits unsoundness without type assertions is a little much; you might as well just assert and move on:
const setValue = <K extends keyof Foo>(key: K, value: Foo[K][number]) => { foo[key] = [value] as Foo[K]; // no compiler error }
It would be nice if there were something better here, but I don't know what that would be.
Reacted by Bruce Pascoe and DetachHead#30769 is related, as it's the source of the intersection behavior you see (
number[] & string[]) in the OP. Prior to that PR being merged, patterns like this actually used to work.So, TS is protecting you from doing this (unlikely in practice) terrible thing
I'm not convinced it's that unlikely either. While nobody is going to write
Math.random() < 0.999 ? "a" : "b"for the key, they may be passing the key in via a variable typed askeyof Fooand then all bets are off (worst case scenario: one of the properties is typed asunknownand now you can pass in anything at all for the value). I feel like the "correct" (i.e. most elegant) solution to this problem would be to implementoneofconstraints, wherein the compiler can know for sure thatT[K]isn't going to be a union of several property types.Reacted by DetachHead and Andrii Dieiev- addedDuplicateAn existing issue was already createdAn existing issue was already created
on Nov 19, 2022 typescript-bot commented
on Nov 21, 2022 ContributorMore actionsThis issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
- locked as resolved and limited conversation to collaborators
on Oct 22, 2025
Bug Report
๐ Search Terms
index type generic
๐ Version & Regression Information
4.9.0-dev.20221025
โฏ Playground Link
Playground link with relevant code
๐ป Code
๐ Actual behavior
๐ Expected behavior
no error, since it works when the type of value is
Foo[T]: