I have a typing issue with my generic function. The issue appears though several third-party libraries, but I managed to build minimal example.
interface MyType {
id: string;
}
function myFunction<T extends MyType>(param: T) {
// Type 'string' is not assignable to type 'NonNullable<T["id"]>'.
const a: NonNullable<T["id"]> = "my-id";
console.log(a);
}
If I rewrite this function without generic, typing works fine:
function myFunction(param: MyType) {
const a: NonNullable<MyType["id"]> = "my-id";
console.log(a);
}
I checked generics documentation and can't find why the first code is incorrect. Can someone help me with that?
Up to and including TypeScript 4.7, the NonNullable<T> utility type was implemented as a conditional type, T extends null | undefined ? never : T.
When a conditional type depends on a generic type parameter, as in the type NonNullable<T["id"]> inside the body of myFunction(), the compiler completely defers evaluation of it; it just doesn't know what type it is yet, at all. Such types are essentially opaque to the compiler, so it cannot verify that a value of any other type is assignable to it. You need to refactor or use type assertions to perform such an assignment:
const a = "my-id" as NonNullable<T["id"]>; // okay, TS4.7-
Luckily, TypeScript 4.8 will introduce some better intersection reduction and narrowing support that includes re-defining NonNullable<T> as T & {}. That is, instead of a conditional type, it's just an intersection with the empty object type (which can be anything except null or undefined). Details about how this works are in microsoft/TypeScript#49119, but for your purposes all you need to know is that your original example will just start working:
const a: NonNullable<T["id"]> = "my-id"; // okay TS4.8+
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