Home > OS >  Why behave <T extends Record<number, X>> and <T extends Record<string, X>> d
Why behave <T extends Record<number, X>> and <T extends Record<string, X>> d

Time:06-24

When I do this:

interface I<T extends Record<string, {b: boolean}>> {
  x: T[keyof T]['b'];
}

everything works, but as soon as I change string to number, I get ts(2536): Type 'b' cannot be used to index type 'T[keyof T]'. Why is that?

CodePudding user response:

I believe the reason for this behaviour is that Record<number, any> also does accept array types.

Doing I<{b: boolean}[]> compiles correctly. But using keyof on an array type will also include a lot of other properties like length or method keys like pop() or push(). So when you do T[keyof T], you will also get the types of these properties.

type ArrayValueof = {b: boolean}[][keyof []]
// type ArrayValueof = number | {
//     b: boolean;
// } | (() => IterableIterator<{
//     b: boolean;
// }>) | (() => {
//     copyWithin: boolean;
//     entries: boolean;
//     fill: boolean;
//     find: boolean;
//     findIndex: boolean;
//     keys: boolean;
//     values: boolean;
// }) | ... 28 more ... | ((searchElement: {
//     b: boolean;
// }, fromIndex?: number | undefined) => boolean)

As you can see, { b: boolean } is part of this union but not every element in this union is an object with the property b. That's why you can not use "b" to index T[keyof T] when T also might be an array.

Playground


You can fix this issue by using an additional check:

interface I<T extends Record<number, {b: boolean}>> {
  x: T[T extends any[] ? number : keyof T]["b"];
}

Playground

  • Related