Reputation: 1670
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?
Upvotes: 4
Views: 708
Reputation: 328548
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+
Upvotes: 1