In the code below:
interface MyInterface {
a: {
b: {
c: "c";
};
};
}
type ParentProps = keyof MyInterface
type ChildProps<ParentProp extends ParentProps> = keyof MyInterface[ParentProp]
type GrandChildType<ParentProp extends ParentProps, ChildProp extends ChildProps<ParentProp>> =
MyInterface[ParentProp][ChildProp]['c']
I'm getting an error in the GrandChildType saying:
Type '"c"' cannot be used to index type 'MyInterface[ParentProp][ChildProp]'.(2536)
However, if I change the last line to:
type GrandChildType<ParentProp extends ParentProps, ChildProp extends ChildProps<ParentProp>> =
MyInterface[ParentProp][ChildProp]
type test = GrandChildType<'a', 'b'>['c']
then I properly get the type of c which is "c". So, the question is: why can't I get that value type when using Generics if both ParentProps and ChildProps are keys extracted from MyInterface?
reproducible link: typescript playground
This seems to be a bug in TypeScript; see microsoft/TypeScript#27709 for details. Nested generic indexed access types seem to "forget" their constraint and won't let you index into them with a specific key anymore. The bug has been open for a long time with no sign that it will be addressed soon, so you might want to work around it.
In cases where you have a type T where you know T extends U is true but the compiler doesn't, you can often replace T with Extract<T, U> using the Extract<T, U> utility type. The compiler will accept that Extract<T, U> extends U, and assuming you're correct about T extends U, then eventually (when the generics are specified), Extract<T, U> will evaluate to just T. (Note that this is an "off-label" use of Extract<T, U> which is used primarily to filter unions in T to just those members which are assignable to U.)
In order to index into some type T with the key type "c", it means the type should be assignable to {c?: any}. If T["c"] fails to compile, then Extract<T , {c?: any}> should succeed. Let's try it here:
type GrandChildType<K extends keyof MyInterface, K2 extends keyof MyInterface[K]> =
Extract<MyInterface[K][K2], { c?: any }>['c']; // okay
type C = GrandChildType<"a", "b"> // "c"
Looks good! The type of GrandChildType<"a", "b"> is "c" as desired, and the error in GrandChildType is gone.
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With