The TypeScript type checker cannot really accurately determine when a specific value is or is not assignable to a generic type. There are a very few operations on generics that TypeScript can "reason" about properly. For most operations, it either treats the generic type as opaque and is unable to see any non-identical type as being assignable, or it gives up and replaces the generic type parameters with their constraints. In the former case this is very strict and can lead to false positives (errors on safe code), while the latter end up being too lax and lead to false negatives (failure to error on unsafe code).
The former situation is what happens with
const condition: Partial<T> = {
[pk]: undefined
}; // error!
The compiler treats Partial<T>
as mostly opaque and doesn't accept {[pk]: undefined}
as a possible value, even though undefined
should be allowed as a value of any property of Partial<T>
for any T
(assuming you have not enabled the --exactOptionalPropertyTypes
compiler option, that is). Indeed if you change T
to some specific type, then the assignment will succeed. But the compiler is unable to reason that undefined
is allowed for the generic case. This is as described in microsoft/TypeScript#22229. If you want the compiler to accept that, you should use a type assertion:
const condition = {
[pk]: undefined
} as Partial<T>;
The latter situation is what happens with
const condition: Partial<T> = {};
condition[pk] = undefined;
The compiler treats {}
as one of the few valid types assignable to Partial<T>
for generic T
(this had to be explicitly implemented), and then when you assign to condition[pk]
the compiler is lax and allows undefined
to be assigned. This happens to be safe, but the compiler isn't really checking it properly.
This is as described in microsoft/TypeScript#30989. A similar situation happens here:
const compare = <T extends {a: string}>(s: string) => {
const p: Partial<T> = {a: s}; // error
const o: Partial<T> = {};
o.a = s; // okay
}
The p
line gives an error while the o
code accepts it, but in this case it is the error that's correct, because maybe T
will be specified with a type like {a: "x" | "y"}
and calling compare<{a: "x" | "y"}>("z")
will end up trying to assign "z"
to a place that should be "x" | "y"
. See Why can't I return a generic 'T' to satisfy a Partial<T>? for more information about that.
So that's what's going on. The compiler can't really do accurate analysis on arbitrary generic operations, so it takes shortcuts. If it complains about something you know to be safe, you can use a type assertion to suppress it.
Playground link to code