TypeScript Version: 3.9.2
Search Terms: incorrect code hint for mapped keyof property types
Code
type Numbers = {
[key: string]: number
}
type Strings<T extends Numbers> = {
[K in keyof T]: string
}
function test<T extends Numbers>(input: T): Strings<T> {
const output = {} as any
for (const key of Object.keys(input)) {
output[key] = input[key].toString()
}
return output
}
// hover over `mapped` to see the property type is `number` (incorrect)
const mapped = test({ value: 100 })
// hover over `value` to see the property type is `string` (correct)
mapped.value
Expected behavior:
Code hint should show property types as string
Actual behavior:
Code hint shows property types as number
However, hovering over a property shows its type as string
Playground Link:
This example code highlights the issue
It seems that
{
value: number;
}
in
const mapped: Strings<{
value: number;
}>
in the mapped's hint defines the test function input's (T) type, not the __return type__ of the function.
I'm curious if there is any way to return the result type of Strings<T> from the function instead of the "unresolved" one to improve the hint. It would be great.
The tooltip says, reading it in its entirety, Strings<{ value: number }>, which is exactly what this type is.
The tooltip says, reading it in its entirety,
Strings<{ value: number }>, which is exactly what this type is.
@RyanCavanaugh, could you please explain how that is the case when the test function is returning a [key: string]: string type? I assume there is a misunderstanding on my part regarding the way [K in keyof T] works when mapping objects this way
@simon-robertson The type you are seeing Strings<{ value: number }> resolves to { value: string; } but TS does not always expand out type aliases in tooltips. So there is no conflict between what you are seeing.
If you want to always expand out a type alias add {} & to the mapped type to force expansion (note that {} & is not documented and should not be relied upon, it's just a useful debugging trick)
type Numbers = {
[key: string]: number
}
type Strings<T extends Numbers> = {} & {
[K in keyof T]: string
}
function test<T extends Numbers>(input: T): Strings<T> {
const output = {} as any
for (const key of Object.keys(input)) {
output[key] = input[key].toString()
}
return output
}
// Now you see { value: string; }
const mapped = test({ value: 100 })
mapped.value // string
Thank you for the explanation @dragomirtitian, using {} & does indeed work even though its use in this case is counter-intuitive
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
Most helpful comment
@simon-robertson The type you are seeing
Strings<{ value: number }>resolves to{ value: string; }but TS does not always expand out type aliases in tooltips. So there is no conflict between what you are seeing.If you want to always expand out a type alias add
{} &to the mapped type to force expansion (note that{} &is not documented and should not be relied upon, it's just a useful debugging trick)Playground Link