The compiler just isn't clever enough. Conditional types that depend on generic parameters (like KeysOfType<T, string>
) are generally treated by the compiler as somewhat opaque, and while you understand that KeysOfType<T, V>
was specifically constructed so as to make sure that T[KeysOfType<T, V>] extends V
is true, the compiler doesn't even try.
The most general solution available to us in cases like this is to use a type assertion. For example, you can tell the compiler not to worry, and to treat obj[p]
as a string
:
function getLen<T>(obj: T, p: KeysOfType<T, string>): number {
return (obj[p] as unknown as string).length;
// the type T[{ [P in keyof T]: T[P] extends string ? P : never; }[keyof T]]
// is so opaque to the compiler that we must widen to unknown
// before narrowing to string
}
Note that you are relieving the compiler of the duty of verifying type safety. You could just as easily had said obj[p] as unknown as boolean
and the compiler would have believed you. So use this power with caution.
Another way to do a similar thing is to use a single function overload to distinguish between the generic conditional type as seen by the caller and the hopefully more tractable type as seen by the implementation:
// call signature, unchanged
function getLen<T>(obj: T, p: KeysOfType<T, string>): number;
// implementation signature... let's make p the generic type K
// and say that obj has keys K and values of type string
function getLen<K extends keyof any>(obj: Record<K, string>, p: K): number {
return obj[p].length;
}
The reason that it's similar to a type assertion is because the compiler allows you to make the implementation signature looser than the call signatures... if you're not careful you can lie to the compiler and you won't see a problem until runtime.
Okay, hope that helps. Good luck!