An issue was identified in TypeScript where the behavior was initially seen as a bug, which was reported and fixed in versions microsoft/TypeScript#45122 and microsoft/TypeScript#45263 respectively, with the release of TypeScript 4.5.
The question arises on how TypeScript should handle the intersections of object types containing properties with the same key, some being readonly while others are not. The debate is whether the resulting property should be readonly or not. For instance, should
{readonly a: string} & {a: string}
result in
{readonly a: string}
or
{a: string}
?
In essence, despite its name, readonly
signifies "this is readable," whereas non-readonly
indicates "this is readable and writable." In the context of intersections meaning "and,"
{readonly a: string} & {a: string}
would ideally have a property
a
that is both readable and writable, leading to the conclusion that the final type should be
{a: string}
without the readonly modifier. This implies that a property should only be marked readonly if it holds that attribute in every member of the intersection where it appears. Intersections cannot introduce the readonly modifier to a property that is not originally defined as readonly.
Prior to TypeScript 4.5, there was an incorrect implementation where a property in the final object became readonly if it held that attribute in any one of the intersecting members. This behavior was rectified in later versions.
This change in behavior in TypeScript 4.5 explains why certain code implementations were impacted. For example, something like T & Readonly<Pick<T, K>>
should not be able to add readonly to keys in K as they are also part of keyof T. However,
Omit<T, K> & Readonly<Pick<T, K>>
can include readonly modifiers since K's keys are absent from keyof Omit
. You can achieve similar results with Omit<T, K> & Readonly<T>
without needing to exclude other keys.